diff --git a/.dockerignore b/.dockerignore index 0adca0b324..6f3c903f0f 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,7 +3,6 @@ /public/build /public/hot /public/storage -/storage/*.key /vendor .env .env.backup @@ -25,3 +24,15 @@ yarn-error.log .ignition.json .env.dusk.local docker/coolify-realtime/node_modules + +/storage/*.key +/storage/app/backups +/storage/app/ssh/keys +/storage/app/ssh/mux +/storage/app/tmp +/storage/app/debugbar +/storage/logs +/storage/pail + + + diff --git a/.github/workflows/coolify-production-build.yml b/.github/workflows/coolify-production-build.yml index 5271143ec3..d7244fc849 100644 --- a/.github/workflows/coolify-production-build.yml +++ b/.github/workflows/coolify-production-build.yml @@ -47,7 +47,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . - file: docker/prod/Dockerfile + file: docker/production/Dockerfile platforms: linux/amd64 push: true tags: | @@ -82,7 +82,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . - file: docker/prod/Dockerfile + file: docker/production/Dockerfile platforms: linux/aarch64 push: true tags: | diff --git a/.github/workflows/coolify-staging-build.yml b/.github/workflows/coolify-staging-build.yml index 2c57a36a31..bcb65ecbfb 100644 --- a/.github/workflows/coolify-staging-build.yml +++ b/.github/workflows/coolify-staging-build.yml @@ -42,7 +42,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . - file: docker/prod/Dockerfile + file: docker/production/Dockerfile platforms: linux/amd64 push: true tags: | @@ -75,7 +75,7 @@ jobs: uses: docker/build-push-action@v6 with: context: . - file: docker/prod/Dockerfile + file: docker/production/Dockerfile platforms: linux/aarch64 push: true tags: | diff --git a/README.md b/README.md index dac48d127a..56edffd31c 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,8 @@ Special thanks to our biggest sponsors! ### Special Sponsors -![image](https://github.com/user-attachments/assets/726fb63e-c3b8-4260-b3ac-06780605ec5d) +![image](https://github.com/user-attachments/assets/6022bc9c-8435-4d14-9497-8be230ed8cb1) + * [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. @@ -52,7 +53,9 @@ Special thanks to our biggest sponsors! * [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. -* [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies. +* [Cloudify.ro](https://cloudify.ro/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes. +* [Syntaxfm](https://syntax.fm/?ref=coolify.io) - Podcast for web developers. +* [PFGlabs](https://pfglabs.com/?ref=coolify.io) - Build real project with Golang. * [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. * [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities. @@ -92,6 +95,9 @@ Special thanks to our biggest sponsors! Michael Mazurczak Formbricks StartupFame +jyc.dev +BitLaunch +Internet Garden Jonas Jaeger JP Evercam diff --git a/app/Actions/Proxy/StartProxy.php b/app/Actions/Proxy/StartProxy.php index 7c93720cbf..9bc506d9b5 100644 --- a/app/Actions/Proxy/StartProxy.php +++ b/app/Actions/Proxy/StartProxy.php @@ -2,6 +2,7 @@ namespace App\Actions\Proxy; +use App\Enums\ProxyTypes; use App\Events\ProxyStarted; use App\Models\Server; use Lorisleiva\Actions\Concerns\AsAction; @@ -37,11 +38,16 @@ public function handle(Server $server, bool $async = true, bool $force = false): "echo 'Successfully started coolify-proxy.'", ]); } else { - $caddfile = 'import /dynamic/*.caddy'; + if (isDev()) { + if ($proxyType === ProxyTypes::CADDY->value) { + $proxy_path = '/data/coolify/proxy/caddy'; + } + } + $caddyfile = 'import /dynamic/*.caddy'; $commands = $commands->merge([ "mkdir -p $proxy_path/dynamic", "cd $proxy_path", - "echo '$caddfile' > $proxy_path/dynamic/Caddyfile", + "echo '$caddyfile' > $proxy_path/dynamic/Caddyfile", "echo 'Creating required Docker Compose file.'", "echo 'Pulling docker image.'", 'docker compose pull', diff --git a/app/Console/Commands/CleanupUnreachableServers.php b/app/Console/Commands/CleanupUnreachableServers.php index df0c6b81ba..def01b2651 100644 --- a/app/Console/Commands/CleanupUnreachableServers.php +++ b/app/Console/Commands/CleanupUnreachableServers.php @@ -18,7 +18,6 @@ public function handle() if ($servers->count() > 0) { foreach ($servers as $server) { echo "Cleanup unreachable server ($server->id) with name $server->name"; - // send_internal_notification("Server $server->name is unreachable for 7 days. Cleaning up..."); $server->update([ 'ip' => '1.2.3.4', ]); diff --git a/app/Console/Commands/Dev.php b/app/Console/Commands/Dev.php index 962000d070..257de0a927 100644 --- a/app/Console/Commands/Dev.php +++ b/app/Console/Commands/Dev.php @@ -76,7 +76,5 @@ public function init() } else { echo "Instance already initialized.\n"; } - // Set permissions - Process::run(['chmod', '-R', 'o+rwx', '.']); } } diff --git a/app/Console/Commands/Emails.php b/app/Console/Commands/Emails.php index f0e0e7fa07..33ddf3019f 100644 --- a/app/Console/Commands/Emails.php +++ b/app/Console/Commands/Emails.php @@ -2,14 +2,12 @@ namespace App\Console\Commands; -use App\Jobs\SendConfirmationForWaitlistJob; use App\Models\Application; use App\Models\ApplicationPreview; use App\Models\ScheduledDatabaseBackup; use App\Models\Server; use App\Models\StandalonePostgresql; use App\Models\Team; -use App\Models\Waitlist; use App\Notifications\Application\DeploymentFailed; use App\Notifications\Application\DeploymentSuccess; use App\Notifications\Application\StatusChanged; @@ -64,8 +62,6 @@ public function handle() 'backup-success' => 'Database - Backup Success', 'backup-failed' => 'Database - Backup Failed', // 'invitation-link' => 'Invitation Link', - 'waitlist-invitation-link' => 'Waitlist Invitation Link', - 'waitlist-confirmation' => 'Waitlist Confirmation', 'realusers-before-trial' => 'REAL - Registered Users Before Trial without Subscription', 'realusers-server-lost-connection' => 'REAL - Server Lost Connection', ], @@ -187,7 +183,7 @@ public function handle() 'team_id' => 0, ]); } - // $this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail(); + //$this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail(); $this->sendEmail(); break; // case 'invitation-link': @@ -204,23 +200,6 @@ public function handle() // $this->mail = (new InvitationLink($user))->toMail(); // $this->sendEmail(); // break; - case 'waitlist-invitation-link': - $this->mail = new MailMessage; - $this->mail->view('emails.waitlist-invitation', [ - 'loginLink' => 'https://coolify.io', - ]); - $this->mail->subject('Congratulations! You are invited to join Coolify Cloud.'); - $this->sendEmail(); - break; - case 'waitlist-confirmation': - $found = Waitlist::where('email', $this->email)->first(); - if ($found) { - SendConfirmationForWaitlistJob::dispatch($this->email, $found->uuid); - } else { - throw new Exception('Waitlist not found'); - } - - break; case 'realusers-before-trial': $this->mail = new MailMessage; $this->mail->view('emails.before-trial-conversion'); diff --git a/app/Console/Commands/Horizon.php b/app/Console/Commands/Horizon.php index 655729ec97..d3e35ca5a1 100644 --- a/app/Console/Commands/Horizon.php +++ b/app/Console/Commands/Horizon.php @@ -13,7 +13,7 @@ class Horizon extends Command public function handle() { if (config('constants.horizon.is_horizon_enabled')) { - $this->info('[x]: Horizon is enabled. Starting.'); + $this->info('Horizon is enabled on this server.'); $this->call('horizon'); exit(0); } else { diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php index 2162628191..cc9bee0a53 100644 --- a/app/Console/Commands/Init.php +++ b/app/Console/Commands/Init.php @@ -55,10 +55,8 @@ public function handle() } else { $this->cleanup_in_progress_application_deployments(); } - echo "[3]: Cleanup Redis keys.\n"; $this->call('cleanup:redis'); - echo "[4]: Cleanup stucked resources.\n"; $this->call('cleanup:stucked-resources'); try { @@ -114,7 +112,6 @@ private function pullTemplatesFromCDN() private function optimize() { - echo "[1]: Optimizing Laravel (caching config, routes, views).\n"; Artisan::call('optimize:clear'); Artisan::call('optimize'); } @@ -189,7 +186,6 @@ private function cleanup_unused_network_from_coolify_proxy() } } if ($commands->isNotEmpty()) { - echo "Cleaning up unused networks from coolify proxy\n"; remote_process(command: $commands, type: ActivityTypes::INLINE->value, server: $server, ignore_errors: false); } } catch (\Throwable $e) { @@ -232,15 +228,14 @@ private function send_alive_signal() $settings = instanceSettings(); $do_not_track = data_get($settings, 'do_not_track'); if ($do_not_track == true) { - echo "[2]: Skipping sending live signal as do_not_track is enabled\n"; + echo "Do_not_track is enabled\n"; return; } try { Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version"); - echo "[2]: Sending live signal!\n"; } catch (\Throwable $e) { - echo "[2]: Error in sending live signal: {$e->getMessage()}\n"; + echo "Error in sending live signal: {$e->getMessage()}\n"; } } @@ -253,7 +248,6 @@ private function cleanup_in_progress_application_deployments() } $queued_inprogress_deployments = ApplicationDeploymentQueue::whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value])->get(); foreach ($queued_inprogress_deployments as $deployment) { - echo "Cleaning up deployment: {$deployment->id}\n"; $deployment->status = ApplicationDeploymentStatus::FAILED->value; $deployment->save(); } diff --git a/app/Console/Commands/Migration.php b/app/Console/Commands/Migration.php new file mode 100644 index 0000000000..44c17203bf --- /dev/null +++ b/app/Console/Commands/Migration.php @@ -0,0 +1,24 @@ +info('Migration is enabled on this server.'); + $this->call('migrate', ['--force' => true, '--isolated' => true]); + exit(0); + } else { + $this->info('Migration is disabled on this server.'); + exit(0); + } + } +} diff --git a/app/Console/Commands/NotifyDemo.php b/app/Console/Commands/NotifyDemo.php index f0131b7b2a..990a03869c 100644 --- a/app/Console/Commands/NotifyDemo.php +++ b/app/Console/Commands/NotifyDemo.php @@ -59,9 +59,10 @@ private function showHelp()
Channels:
@@ -72,6 +73,6 @@ private function showHelp()
In which manner you wish a coolified notification?
- HTML, ['email', 'slack', 'discord', 'telegram']); + HTML, ['email', 'discord', 'telegram', 'slack', 'pushover']); } } diff --git a/app/Console/Commands/Scheduler.php b/app/Console/Commands/Scheduler.php index 9ee7b06e6f..ee64368c3d 100644 --- a/app/Console/Commands/Scheduler.php +++ b/app/Console/Commands/Scheduler.php @@ -13,7 +13,7 @@ class Scheduler extends Command public function handle() { if (config('constants.horizon.is_scheduler_enabled')) { - $this->info('[x]: Scheduler is enabled. Starting.'); + $this->info('Scheduler is enabled on this server.'); $this->call('schedule:work'); exit(0); } else { diff --git a/app/Console/Commands/Seeder.php b/app/Console/Commands/Seeder.php new file mode 100644 index 0000000000..e37b6a9d27 --- /dev/null +++ b/app/Console/Commands/Seeder.php @@ -0,0 +1,24 @@ +info('Seeder is enabled on this server.'); + $this->call('db:seed', ['--class' => 'ProductionSeeder', '--force' => true]); + exit(0); + } else { + $this->info('Seeder is disabled on this server.'); + exit(0); + } + } +} diff --git a/app/Console/Commands/WaitlistInvite.php b/app/Console/Commands/WaitlistInvite.php deleted file mode 100644 index 2e330068c8..0000000000 --- a/app/Console/Commands/WaitlistInvite.php +++ /dev/null @@ -1,114 +0,0 @@ -option('people'); - for ($i = 0; $i < $people; $i++) { - $this->main(); - } - } - - private function main() - { - if ($this->argument('email')) { - if ($this->option('only-email')) { - $this->next_patient = User::whereEmail($this->argument('email'))->first(); - $this->password = Str::password(); - $this->next_patient->update([ - 'password' => Hash::make($this->password), - 'force_password_reset' => true, - ]); - } else { - $this->next_patient = Waitlist::where('email', $this->argument('email'))->first(); - } - if (! $this->next_patient) { - $this->error("{$this->argument('email')} not found in the waitlist."); - - return; - } - } else { - $this->next_patient = Waitlist::orderBy('created_at', 'asc')->where('verified', true)->first(); - } - if ($this->next_patient) { - if ($this->option('only-email')) { - $this->send_email(); - - return; - } - $this->register_user(); - $this->remove_from_waitlist(); - $this->send_email(); - } else { - $this->info('No verified user found in the waitlist. 👀'); - } - } - - private function register_user() - { - $already_registered = User::whereEmail($this->next_patient->email)->first(); - if (! $already_registered) { - $this->password = Str::password(); - User::create([ - 'name' => str($this->next_patient->email)->before('@'), - 'email' => $this->next_patient->email, - 'password' => Hash::make($this->password), - 'force_password_reset' => true, - ]); - $this->info("User registered ({$this->next_patient->email}) successfully. 🎉"); - } else { - throw new \Exception('User already registered'); - } - } - - private function remove_from_waitlist() - { - $this->next_patient->delete(); - $this->info('User removed from waitlist successfully.'); - } - - private function send_email() - { - $token = Crypt::encryptString("{$this->next_patient->email}@@@$this->password"); - $loginLink = route('auth.link', ['token' => $token]); - $mail = new MailMessage; - $mail->view('emails.waitlist-invitation', [ - 'loginLink' => $loginLink, - ]); - $mail->subject('Congratulations! You are invited to join Coolify Cloud.'); - send_user_an_email($mail, $this->next_patient->email); - $this->info('Email sent successfully. 📧'); - } -} diff --git a/app/Console/Commands/Weird.php b/app/Console/Commands/Weird.php deleted file mode 100644 index e471a5f961..0000000000 --- a/app/Console/Commands/Weird.php +++ /dev/null @@ -1,58 +0,0 @@ -error('This command can only be run in development mode'); - - return; - } - $run = $this->option('run'); - if ($run) { - $servers = Server::all(); - foreach ($servers as $server) { - ServerCheck::dispatch($server); - } - - return; - } - $number = $this->option('number'); - for ($i = 0; $i < $number; $i++) { - $uuid = Str::uuid(); - $server = Server::create([ - 'name' => 'localhost-'.$uuid, - 'description' => 'This is a test docker container in development mode', - 'ip' => 'coolify-testing-host', - 'team_id' => 0, - 'private_key_id' => 1, - 'proxy' => [ - 'type' => ProxyTypes::NONE->value, - 'status' => ProxyStatus::EXITED->value, - ], - ]); - $server->settings->update([ - 'is_usable' => true, - 'is_reachable' => true, - ]); - } - } catch (\Exception $e) { - $this->error($e->getMessage()); - } - } -} diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index 35ff2632dc..f02c4255dd 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -25,26 +25,24 @@ class ApplicationsController extends Controller { private function removeSensitiveData($application) { - $token = auth()->user()->currentAccessToken(); $application->makeHidden([ 'id', ]); - if ($token->can('view:sensitive')) { - return serializeApiResponse($application); + if (request()->attributes->get('can_read_sensitive', false) === false) { + $application->makeHidden([ + 'custom_labels', + 'dockerfile', + 'docker_compose', + 'docker_compose_raw', + 'manual_webhook_secret_bitbucket', + 'manual_webhook_secret_gitea', + 'manual_webhook_secret_github', + 'manual_webhook_secret_gitlab', + 'private_key_id', + 'value', + 'real_value', + ]); } - $application->makeHidden([ - 'custom_labels', - 'dockerfile', - 'docker_compose', - 'docker_compose_raw', - 'manual_webhook_secret_bitbucket', - 'manual_webhook_secret_gitea', - 'manual_webhook_secret_github', - 'manual_webhook_secret_gitlab', - 'private_key_id', - 'value', - 'real_value', - ]); return serializeApiResponse($application); } diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index 9366e63006..917171e5cd 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -19,26 +19,23 @@ class DatabasesController extends Controller { private function removeSensitiveData($database) { - $token = auth()->user()->currentAccessToken(); $database->makeHidden([ 'id', 'laravel_through_key', ]); - if ($token->can('view:sensitive')) { - return serializeApiResponse($database); + if (request()->attributes->get('can_read_sensitive', false) === false) { + $database->makeHidden([ + 'internal_db_url', + 'external_db_url', + 'postgres_password', + 'dragonfly_password', + 'redis_password', + 'mongo_initdb_root_password', + 'keydb_password', + 'clickhouse_admin_password', + ]); } - $database->makeHidden([ - 'internal_db_url', - 'external_db_url', - 'postgres_password', - 'dragonfly_password', - 'redis_password', - 'mongo_initdb_root_password', - 'keydb_password', - 'clickhouse_admin_password', - ]); - return serializeApiResponse($database); } @@ -211,8 +208,9 @@ public function database_by_uuid(Request $request) 'mongo_conf' => ['type' => 'string', 'description' => 'Mongo conf'], 'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'Mongo initdb root username'], 'mongo_initdb_root_password' => ['type' => 'string', 'description' => 'Mongo initdb root password'], - 'mongo_initdb_init_database' => ['type' => 'string', 'description' => 'Mongo initdb init database'], + 'mongo_initdb_database' => ['type' => 'string', 'description' => 'Mongo initdb init database'], 'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'], + 'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'], 'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'], 'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'], 'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'], @@ -241,7 +239,7 @@ public function database_by_uuid(Request $request) )] public function update_by_uuid(Request $request) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); @@ -413,12 +411,12 @@ public function update_by_uuid(Request $request) } break; case 'standalone-mongodb': - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database']; $validator = customApiValidator($request->all(), [ 'mongo_conf' => 'string', 'mongo_initdb_root_username' => 'string', 'mongo_initdb_root_password' => 'string', - 'mongo_initdb_init_database' => 'string', + 'mongo_initdb_database' => 'string', ]); if ($request->has('mongo_conf')) { if (! isBase64Encoded($request->mongo_conf)) { @@ -443,9 +441,10 @@ public function update_by_uuid(Request $request) break; case 'standalone-mysql': - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; $validator = customApiValidator($request->all(), [ 'mysql_root_password' => 'string', + 'mysql_password' => 'string', 'mysql_user' => 'string', 'mysql_database' => 'string', 'mysql_conf' => 'string', @@ -909,6 +908,7 @@ public function create_database_mariadb(Request $request) 'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], 'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], 'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'], + 'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'], 'mysql_user' => ['type' => 'string', 'description' => 'MySQL user'], 'mysql_database' => ['type' => 'string', 'description' => 'MySQL database'], 'mysql_conf' => ['type' => 'string', 'description' => 'MySQL conf'], @@ -1013,7 +1013,7 @@ public function create_database_mongodb(Request $request) public function create_database(Request $request, NewDatabaseTypes $type) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database', 'mysql_root_password', 'mysql_user', 'mysql_database', 'mysql_conf']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { @@ -1220,9 +1220,10 @@ public function create_database(Request $request, NewDatabaseTypes $type) return response()->json(serializeApiResponse($payload))->setStatusCode(201); } elseif ($type === NewDatabaseTypes::MYSQL) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_user', 'mysql_database', 'mysql_conf']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; $validator = customApiValidator($request->all(), [ 'mysql_root_password' => 'string', + 'mysql_password' => 'string', 'mysql_user' => 'string', 'mysql_database' => 'string', 'mysql_conf' => 'string', @@ -1456,12 +1457,12 @@ public function create_database(Request $request, NewDatabaseTypes $type) return response()->json(serializeApiResponse($payload))->setStatusCode(201); } elseif ($type === NewDatabaseTypes::MONGODB) { - $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_init_database']; + $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database']; $validator = customApiValidator($request->all(), [ 'mongo_conf' => 'string', 'mongo_initdb_root_username' => 'string', 'mongo_initdb_root_password' => 'string', - 'mongo_initdb_init_database' => 'string', + 'mongo_initdb_database' => 'string', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); if ($validator->fails() || ! empty($extraFields)) { diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 666dc55a53..73b452f865 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -16,15 +16,12 @@ class DeployController extends Controller { private function removeSensitiveData($deployment) { - $token = auth()->user()->currentAccessToken(); - if ($token->can('view:sensitive')) { - return serializeApiResponse($deployment); + if (request()->attributes->get('can_read_sensitive', false) === false) { + $deployment->makeHidden([ + 'logs', + ]); } - $deployment->makeHidden([ - 'logs', - ]); - return serializeApiResponse($deployment); } diff --git a/app/Http/Controllers/Api/SecurityController.php b/app/Http/Controllers/Api/SecurityController.php index b7190ab1e1..a14b0da20e 100644 --- a/app/Http/Controllers/Api/SecurityController.php +++ b/app/Http/Controllers/Api/SecurityController.php @@ -11,13 +11,11 @@ class SecurityController extends Controller { private function removeSensitiveData($team) { - $token = auth()->user()->currentAccessToken(); - if ($token->can('view:sensitive')) { - return serializeApiResponse($team); + if (request()->attributes->get('can_read_sensitive', false) === false) { + $team->makeHidden([ + 'private_key', + ]); } - $team->makeHidden([ - 'private_key', - ]); return serializeApiResponse($team); } diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index 8c13b1a010..f37040bdd7 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -19,25 +19,22 @@ class ServersController extends Controller { private function removeSensitiveDataFromSettings($settings) { - $token = auth()->user()->currentAccessToken(); - if ($token->can('view:sensitive')) { - return serializeApiResponse($settings); + if (request()->attributes->get('can_read_sensitive', false) === false) { + $settings = $settings->makeHidden([ + 'sentinel_token', + ]); } - $settings = $settings->makeHidden([ - 'sentinel_token', - ]); return serializeApiResponse($settings); } private function removeSensitiveData($server) { - $token = auth()->user()->currentAccessToken(); $server->makeHidden([ 'id', ]); - if ($token->can('view:sensitive')) { - return serializeApiResponse($server); + if (request()->attributes->get('can_read_sensitive', false) === false) { + // Do nothing } return serializeApiResponse($server); diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index bf90322e22..ed9af8c90f 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -18,19 +18,18 @@ class ServicesController extends Controller { private function removeSensitiveData($service) { - $token = auth()->user()->currentAccessToken(); $service->makeHidden([ 'id', ]); - if ($token->can('view:sensitive')) { - return serializeApiResponse($service); + if (request()->attributes->get('can_read_sensitive', false) === false) { + $service->makeHidden([ + 'docker_compose_raw', + 'docker_compose', + 'value', + 'real_value', + ]); } - $service->makeHidden([ - 'docker_compose_raw', - 'docker_compose', - ]); - return serializeApiResponse($service); } diff --git a/app/Http/Controllers/Api/TeamController.php b/app/Http/Controllers/Api/TeamController.php index 3f951c6f7f..d4b24d8ab2 100644 --- a/app/Http/Controllers/Api/TeamController.php +++ b/app/Http/Controllers/Api/TeamController.php @@ -10,20 +10,18 @@ class TeamController extends Controller { private function removeSensitiveData($team) { - $token = auth()->user()->currentAccessToken(); $team->makeHidden([ 'custom_server_limit', 'pivot', ]); - if ($token->can('view:sensitive')) { - return serializeApiResponse($team); + if (request()->attributes->get('can_read_sensitive', false) === false) { + $team->makeHidden([ + 'smtp_username', + 'smtp_password', + 'resend_api_key', + 'telegram_token', + ]); } - $team->makeHidden([ - 'smtp_username', - 'smtp_password', - 'resend_api_key', - 'telegram_token', - ]); return serializeApiResponse($team); } diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 9f1e4eeb81..522683efaa 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -42,15 +42,13 @@ public function verify() public function email_verify(EmailVerificationRequest $request) { $request->fulfill(); - $name = request()->user()?->name; - // send_internal_notification("User {$name} verified their email address."); return redirect(RouteServiceProvider::HOME); } public function forgot_password(Request $request) { - if (is_transactional_emails_active()) { + if (is_transactional_emails_enabled()) { $arrayOfRequest = $request->only(Fortify::email()); $request->merge([ 'email' => Str::lower($arrayOfRequest['email']), diff --git a/app/Http/Controllers/Webhook/Github.php b/app/Http/Controllers/Webhook/Github.php index 3683adaa85..ac1d4ded21 100644 --- a/app/Http/Controllers/Webhook/Github.php +++ b/app/Http/Controllers/Webhook/Github.php @@ -463,7 +463,7 @@ public function redirect(Request $request) $private_key = data_get($data, 'pem'); $webhook_secret = data_get($data, 'webhook_secret'); $private_key = PrivateKey::create([ - 'name' => $slug, + 'name' => "github-app-{$slug}", 'private_key' => $private_key, 'team_id' => $github_app->team_id, 'is_git_related' => true, diff --git a/app/Http/Controllers/Webhook/Waitlist.php b/app/Http/Controllers/Webhook/Waitlist.php deleted file mode 100644 index dec8ca72dd..0000000000 --- a/app/Http/Controllers/Webhook/Waitlist.php +++ /dev/null @@ -1,63 +0,0 @@ -get('email'); - $confirmation_code = request()->get('confirmation_code'); - try { - $found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first(); - if ($found) { - if (! $found->verified) { - if ($found->created_at > now()->subMinutes(config('constants.waitlist.expiration'))) { - $found->verified = true; - $found->save(); - send_internal_notification('Waitlist confirmed: '.$email); - - return 'Thank you for confirming your email address. We will notify you when you are next in line.'; - } else { - $found->delete(); - send_internal_notification('Waitlist expired: '.$email); - - return 'Your confirmation code has expired. Please sign up again.'; - } - } - } - - return redirect()->route('dashboard'); - } catch (Exception $e) { - send_internal_notification('Waitlist confirmation failed: '.$e->getMessage()); - - return redirect()->route('dashboard'); - } - } - - public function cancel(Request $request) - { - $email = request()->get('email'); - $confirmation_code = request()->get('confirmation_code'); - try { - $found = ModelsWaitlist::where('uuid', $confirmation_code)->where('email', $email)->first(); - if ($found && ! $found->verified) { - $found->delete(); - send_internal_notification('Waitlist cancelled: '.$email); - - return 'Your email address has been removed from the waitlist.'; - } - - return redirect()->route('dashboard'); - } catch (Exception $e) { - send_internal_notification('Waitlist cancellation failed: '.$e->getMessage()); - - return redirect()->route('dashboard'); - } - } -} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index 5f1731071e..a1ce20295b 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -69,5 +69,7 @@ class Kernel extends HttpKernel 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'abilities' => \Laravel\Sanctum\Http\Middleware\CheckAbilities::class, 'ability' => \Laravel\Sanctum\Http\Middleware\CheckForAnyAbility::class, + 'api.ability' => \App\Http\Middleware\ApiAbility::class, + 'api.sensitive' => \App\Http\Middleware\ApiSensitiveData::class, ]; } diff --git a/app/Http/Middleware/ApiAbility.php b/app/Http/Middleware/ApiAbility.php new file mode 100644 index 0000000000..324eeebaa3 --- /dev/null +++ b/app/Http/Middleware/ApiAbility.php @@ -0,0 +1,27 @@ +user()->tokenCan('root')) { + return $next($request); + } + + return parent::handle($request, $next, ...$abilities); + } catch (\Illuminate\Auth\AuthenticationException $e) { + return response()->json([ + 'message' => 'Unauthenticated.', + ], 401); + } catch (\Exception $e) { + return response()->json([ + 'message' => 'Missing required permissions: '.implode(', ', $abilities), + ], 403); + } + } +} diff --git a/app/Http/Middleware/ApiSensitiveData.php b/app/Http/Middleware/ApiSensitiveData.php new file mode 100644 index 0000000000..49584ddb3f --- /dev/null +++ b/app/Http/Middleware/ApiSensitiveData.php @@ -0,0 +1,21 @@ +user()->currentAccessToken(); + + // Allow access to sensitive data if token has root or read:sensitive permission + $request->attributes->add([ + 'can_read_sensitive' => $token->can('root') || $token->can('read:sensitive'), + ]); + + return $next($request); + } +} diff --git a/app/Http/Middleware/IgnoreReadOnlyApiToken.php b/app/Http/Middleware/IgnoreReadOnlyApiToken.php deleted file mode 100644 index bd6cd1f8a2..0000000000 --- a/app/Http/Middleware/IgnoreReadOnlyApiToken.php +++ /dev/null @@ -1,28 +0,0 @@ -user()->currentAccessToken(); - if ($token->can('*')) { - return $next($request); - } - if ($token->can('read-only')) { - return response()->json(['message' => 'You are not allowed to perform this action.'], 403); - } - - return $next($request); - } -} diff --git a/app/Http/Middleware/OnlyRootApiToken.php b/app/Http/Middleware/OnlyRootApiToken.php deleted file mode 100644 index 8ff1fa0e54..0000000000 --- a/app/Http/Middleware/OnlyRootApiToken.php +++ /dev/null @@ -1,25 +0,0 @@ -user()->currentAccessToken(); - if ($token->can('*')) { - return $next($request); - } - - return response()->json(['message' => 'You are not allowed to perform this action.'], 403); - } -} diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 41909fa305..6b677fa0e5 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -140,6 +140,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private ?string $buildTarget = null; + private bool $disableBuildCache = false; + private Collection $saved_outputs; private ?string $full_healthcheck_url = null; @@ -178,7 +180,11 @@ public function __construct(int $application_deployment_queue_id) $this->pull_request_id = $this->application_deployment_queue->pull_request_id; $this->commit = $this->application_deployment_queue->commit; $this->rollback = $this->application_deployment_queue->rollback; + $this->disableBuildCache = $this->application->settings->disable_build_cache; $this->force_rebuild = $this->application_deployment_queue->force_rebuild; + if ($this->disableBuildCache) { + $this->force_rebuild = true; + } $this->restart_only = $this->application_deployment_queue->restart_only; $this->restart_only = $this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile'; $this->only_this_server = $this->application_deployment_queue->only_this_server; @@ -1976,6 +1982,9 @@ private function build_image() $this->build_args = $this->build_args->implode(' '); $this->application_deployment_queue->addLogEntry('----------------------------------------'); + if ($this->disableBuildCache) { + $this->application_deployment_queue->addLogEntry('Docker build cache is disabled. It will not be used during the build process.'); + } if ($this->application->build_pack === 'static') { $this->application_deployment_queue->addLogEntry('Static deployment. Copying static assets to the image.'); } else { @@ -2400,7 +2409,7 @@ private function next(string $status) 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/CheckResaleLicenseJob.php b/app/Jobs/CheckResaleLicenseJob.php deleted file mode 100644 index 7479867b63..0000000000 --- a/app/Jobs/CheckResaleLicenseJob.php +++ /dev/null @@ -1,28 +0,0 @@ -getMessage()); - throw $e; - } - } -} diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index ee702202fc..06aec5e495 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -306,7 +306,9 @@ public function handle(): void 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, diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index 80542e03bf..103c137b9f 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -4,7 +4,8 @@ use App\Actions\Server\CleanupDocker; use App\Models\Server; -use App\Notifications\Server\DockerCleanup; +use App\Notifications\Server\DockerCleanupFailed; +use App\Notifications\Server\DockerCleanupSuccess; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldQueue; @@ -12,7 +13,6 @@ use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Log; class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue { @@ -38,35 +38,36 @@ public function handle(): void return; } + $this->usageBefore = $this->server->getDiskUsage(); + if ($this->manualCleanup || $this->server->settings->force_docker_cleanup) { - Log::info('DockerCleanupJob '.($this->manualCleanup ? 'manual' : 'force').' cleanup on '.$this->server->name); CleanupDocker::run(server: $this->server); + $usageAfter = $this->server->getDiskUsage(); + $this->server->team?->notify(new DockerCleanupSuccess($this->server, ($this->manualCleanup ? 'Manual' : 'Forced').' Docker cleanup job executed successfully. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.')); return; } - $this->usageBefore = $this->server->getDiskUsage(); if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) { - Log::info('DockerCleanupJob force cleanup on '.$this->server->name); CleanupDocker::run(server: $this->server); - - return; + $this->server->team?->notify(new DockerCleanupSuccess($this->server, 'Docker cleanup job executed successfully, but no disk usage could be determined.')); } + if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) { CleanupDocker::run(server: $this->server); $usageAfter = $this->server->getDiskUsage(); - if ($usageAfter < $this->usageBefore) { - $this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.')); - Log::info('DockerCleanupJob done: Saved '.($this->usageBefore - $usageAfter).'% disk space on '.$this->server->name); + $diskSaved = $this->usageBefore - $usageAfter; + + if ($diskSaved > 0) { + $this->server->team?->notify(new DockerCleanupSuccess($this->server, 'Saved '.$diskSaved.'% disk space. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.')); } else { - Log::info('DockerCleanupJob failed to save disk space on '.$this->server->name); + $this->server->team?->notify(new DockerCleanupSuccess($this->server, 'Docker cleanup job executed successfully, but no disk space was saved. Disk usage before: '.$this->usageBefore.'%, Disk usage after: '.$usageAfter.'%.')); } } else { - Log::info('No need to clean up '.$this->server->name); + $this->server->team?->notify(new DockerCleanupSuccess($this->server, 'No cleanup needed for '.$this->server->name)); } } catch (\Throwable $e) { - CleanupDocker::run(server: $this->server); - Log::error('DockerCleanupJob failed: '.$e->getMessage()); + $this->server->team?->notify(new DockerCleanupFailed($this->server, 'Docker cleanup job failed with the following error: '.$e->getMessage())); throw $e; } } diff --git a/app/Jobs/ScheduledTaskJob.php b/app/Jobs/ScheduledTaskJob.php index 00575e187c..90a10f3e99 100644 --- a/app/Jobs/ScheduledTaskJob.php +++ b/app/Jobs/ScheduledTaskJob.php @@ -10,6 +10,7 @@ use App\Models\Service; use App\Models\Team; use App\Notifications\ScheduledTask\TaskFailed; +use App\Notifications\ScheduledTask\TaskSuccess; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; @@ -111,6 +112,8 @@ public function handle(): void 'message' => $this->task_output, ]); + $this->team?->notify(new TaskSuccess($this->task, $this->task_output)); + return; } } @@ -125,7 +128,6 @@ public function handle(): void ]); } $this->team?->notify(new TaskFailed($this->task, $e->getMessage())); - // send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage()); throw $e; } finally { ScheduledTaskDone::dispatch($this->team->id); diff --git a/app/Jobs/SendConfirmationForWaitlistJob.php b/app/Jobs/SendConfirmationForWaitlistJob.php deleted file mode 100755 index 7af8205fc3..0000000000 --- a/app/Jobs/SendConfirmationForWaitlistJob.php +++ /dev/null @@ -1,37 +0,0 @@ -email.'&confirmation_code='.$this->uuid; - $cancel_url = base_url().'/webhooks/waitlist/cancel?email='.$this->email.'&confirmation_code='.$this->uuid; - $mail->view('emails.waitlist-confirmation', - [ - 'confirmation_url' => $confirmation_url, - 'cancel_url' => $cancel_url, - ]); - $mail->subject('You are on the waitlist!'); - send_user_an_email($mail, $this->email); - } catch (\Throwable $e) { - send_internal_notification("SendConfirmationForWaitlistJob failed for {$this->email} with error: ".$e->getMessage()); - throw $e; - } - } -} diff --git a/app/Jobs/SendMessageToPushoverJob.php b/app/Jobs/SendMessageToPushoverJob.php new file mode 100644 index 0000000000..834a32b07b --- /dev/null +++ b/app/Jobs/SendMessageToPushoverJob.php @@ -0,0 +1,50 @@ +onQueue('high'); + } + + /** + * Execute the job. + */ + public function handle(): void + { + $response = Http::post('https://api.pushover.net/1/messages.json', $this->message->toPayload($this->token, $this->user)); + if ($response->failed()) { + throw new \RuntimeException('Pushover notification failed with ' . $response->status() . ' status code.' . $response->body()); + } + } +} diff --git a/app/Jobs/SendMessageToSlackJob.php b/app/Jobs/SendMessageToSlackJob.php new file mode 100644 index 0000000000..470002d233 --- /dev/null +++ b/app/Jobs/SendMessageToSlackJob.php @@ -0,0 +1,59 @@ +onQueue('high'); + } + + public function handle(): void + { + Http::post($this->webhookUrl, [ + 'blocks' => [ + [ + 'type' => 'section', + 'text' => [ + 'type' => 'plain_text', + 'text' => 'Coolify Notification', + ], + ], + ], + 'attachments' => [ + [ + 'color' => $this->message->color, + 'blocks' => [ + [ + 'type' => 'header', + 'text' => [ + 'type' => 'plain_text', + 'text' => $this->message->title, + ], + ], + [ + 'type' => 'section', + 'text' => [ + 'type' => 'mrkdwn', + 'text' => $this->message->description, + ], + ], + ], + ], + ], + ]); + } +} diff --git a/app/Jobs/SendMessageToTelegramJob.php b/app/Jobs/SendMessageToTelegramJob.php index 85f4fc934f..6b0a64ae39 100644 --- a/app/Jobs/SendMessageToTelegramJob.php +++ b/app/Jobs/SendMessageToTelegramJob.php @@ -32,7 +32,7 @@ public function __construct( public array $buttons, public string $token, public string $chatId, - public ?string $topicId = null, + public ?string $threadId = null, ) { $this->onQueue('high'); } @@ -67,8 +67,8 @@ public function handle(): void 'chat_id' => $this->chatId, 'text' => $this->text, ]; - if ($this->topicId) { - $payload['message_thread_id'] = $this->topicId; + if ($this->threadId) { + $payload['message_thread_id'] = $this->threadId; } $response = Http::post($url, $payload); if ($response->failed()) { diff --git a/app/Jobs/StripeProcessJob.php b/app/Jobs/StripeProcessJob.php index 00c9b6d18c..d61c738f4a 100644 --- a/app/Jobs/StripeProcessJob.php +++ b/app/Jobs/StripeProcessJob.php @@ -173,8 +173,8 @@ public function handle(): void $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'); + $subscriptionId = data_get($data, 'items.data.0.subscription') ?? data_get($data, 'id'); + $planId = data_get($data, 'items.data.0.plan.id') ?? data_get($data, 'plan.id'); if (Str::contains($excludedPlans, $planId)) { send_internal_notification('Subscription excluded.'); break; @@ -218,9 +218,18 @@ public function handle(): void 'stripe_cancel_at_period_end' => $cancelAtPeriodEnd, ]); if ($status === 'paused' || $status === 'incomplete_expired') { - $subscription->update([ - 'stripe_invoice_paid' => false, - ]); + if ($subscription->stripe_subscription_id === $subscriptionId) { + $subscription->update([ + 'stripe_invoice_paid' => false, + ]); + } + } + if ($status === 'active') { + if ($subscription->stripe_subscription_id === $subscriptionId) { + $subscription->update([ + 'stripe_invoice_paid' => true, + ]); + } } if ($feedback) { $reason = "Cancellation feedback for {$customerId}: '".$feedback."'"; @@ -228,13 +237,24 @@ public function handle(): void $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(); + $subscriptionId = data_get($data, 'id'); + $subscription = Subscription::where('stripe_customer_id', $customerId)->where('stripe_subscription_id', $subscriptionId)->first(); + if ($subscription) { + $team = data_get($subscription, 'team'); + if ($team) { + $team->subscriptionEnded(); + } else { + send_internal_notification('Subscription deleted but no team found in Coolify for customer: '.$customerId); + throw new \RuntimeException("No team found in Coolify for customer: {$customerId}"); + } + } else { + send_internal_notification('Subscription deleted but no subscription found in Coolify for customer: '.$customerId); + throw new \RuntimeException("No subscription found in Coolify for customer: {$customerId}"); + } break; default: throw new \RuntimeException("Unhandled event type: {$type}"); diff --git a/app/Listeners/ProxyStartedNotification.php b/app/Listeners/ProxyStartedNotification.php index d0541b162e..9045b1e5c1 100644 --- a/app/Listeners/ProxyStartedNotification.php +++ b/app/Listeners/ProxyStartedNotification.php @@ -14,7 +14,7 @@ public function __construct() {} public function handle(ProxyStarted $event): void { $this->server = data_get($event, 'data'); - $this->server->setupDefault404Redirect(); + $this->server->setupDefaultRedirect(); $this->server->setupDynamicProxyConfiguration(); $this->server->proxy->force_stop = false; $this->server->save(); diff --git a/app/Livewire/Notifications/Discord.php b/app/Livewire/Notifications/Discord.php index 7a177a2278..57007813ee 100644 --- a/app/Livewire/Notifications/Discord.php +++ b/app/Livewire/Notifications/Discord.php @@ -2,6 +2,7 @@ namespace App\Livewire\Notifications; +use App\Models\DiscordNotificationSettings; use App\Models\Team; use App\Notifications\Test; use Livewire\Attributes\Validate; @@ -11,6 +12,8 @@ class Discord extends Component { public Team $team; + public DiscordNotificationSettings $settings; + #[Validate(['boolean'])] public bool $discordEnabled = false; @@ -18,27 +21,46 @@ class Discord extends Component public ?string $discordWebhookUrl = null; #[Validate(['boolean'])] - public bool $discordNotificationsTest = false; + public bool $deploymentSuccessDiscordNotifications = false; + + #[Validate(['boolean'])] + public bool $deploymentFailureDiscordNotifications = true; + + #[Validate(['boolean'])] + public bool $statusChangeDiscordNotifications = false; + + #[Validate(['boolean'])] + public bool $backupSuccessDiscordNotifications = false; + + #[Validate(['boolean'])] + public bool $backupFailureDiscordNotifications = true; + + #[Validate(['boolean'])] + public bool $scheduledTaskSuccessDiscordNotifications = false; + + #[Validate(['boolean'])] + public bool $scheduledTaskFailureDiscordNotifications = true; #[Validate(['boolean'])] - public bool $discordNotificationsDeployments = false; + public bool $dockerCleanupSuccessDiscordNotifications = false; #[Validate(['boolean'])] - public bool $discordNotificationsStatusChanges = false; + public bool $dockerCleanupFailureDiscordNotifications = true; #[Validate(['boolean'])] - public bool $discordNotificationsDatabaseBackups = false; + public bool $serverDiskUsageDiscordNotifications = true; #[Validate(['boolean'])] - public bool $discordNotificationsScheduledTasks = false; + public bool $serverReachableDiscordNotifications = false; #[Validate(['boolean'])] - public bool $discordNotificationsServerDiskUsage = false; + public bool $serverUnreachableDiscordNotifications = true; public function mount() { try { $this->team = auth()->user()->currentTeam(); + $this->settings = $this->team->discordNotificationSettings; $this->syncData(); } catch (\Throwable $e) { return handleError($e, $this); @@ -49,25 +71,40 @@ public function syncData(bool $toModel = false) { if ($toModel) { $this->validate(); - $this->team->discord_enabled = $this->discordEnabled; - $this->team->discord_webhook_url = $this->discordWebhookUrl; - $this->team->discord_notifications_test = $this->discordNotificationsTest; - $this->team->discord_notifications_deployments = $this->discordNotificationsDeployments; - $this->team->discord_notifications_status_changes = $this->discordNotificationsStatusChanges; - $this->team->discord_notifications_database_backups = $this->discordNotificationsDatabaseBackups; - $this->team->discord_notifications_scheduled_tasks = $this->discordNotificationsScheduledTasks; - $this->team->discord_notifications_server_disk_usage = $this->discordNotificationsServerDiskUsage; - $this->team->save(); + $this->settings->discord_enabled = $this->discordEnabled; + $this->settings->discord_webhook_url = $this->discordWebhookUrl; + + $this->settings->deployment_success_discord_notifications = $this->deploymentSuccessDiscordNotifications; + $this->settings->deployment_failure_discord_notifications = $this->deploymentFailureDiscordNotifications; + $this->settings->status_change_discord_notifications = $this->statusChangeDiscordNotifications; + $this->settings->backup_success_discord_notifications = $this->backupSuccessDiscordNotifications; + $this->settings->backup_failure_discord_notifications = $this->backupFailureDiscordNotifications; + $this->settings->scheduled_task_success_discord_notifications = $this->scheduledTaskSuccessDiscordNotifications; + $this->settings->scheduled_task_failure_discord_notifications = $this->scheduledTaskFailureDiscordNotifications; + $this->settings->docker_cleanup_success_discord_notifications = $this->dockerCleanupSuccessDiscordNotifications; + $this->settings->docker_cleanup_failure_discord_notifications = $this->dockerCleanupFailureDiscordNotifications; + $this->settings->server_disk_usage_discord_notifications = $this->serverDiskUsageDiscordNotifications; + $this->settings->server_reachable_discord_notifications = $this->serverReachableDiscordNotifications; + $this->settings->server_unreachable_discord_notifications = $this->serverUnreachableDiscordNotifications; + + $this->settings->save(); refreshSession(); } else { - $this->discordEnabled = $this->team->discord_enabled; - $this->discordWebhookUrl = $this->team->discord_webhook_url; - $this->discordNotificationsTest = $this->team->discord_notifications_test; - $this->discordNotificationsDeployments = $this->team->discord_notifications_deployments; - $this->discordNotificationsStatusChanges = $this->team->discord_notifications_status_changes; - $this->discordNotificationsDatabaseBackups = $this->team->discord_notifications_database_backups; - $this->discordNotificationsScheduledTasks = $this->team->discord_notifications_scheduled_tasks; - $this->discordNotificationsServerDiskUsage = $this->team->discord_notifications_server_disk_usage; + $this->discordEnabled = $this->settings->discord_enabled; + $this->discordWebhookUrl = $this->settings->discord_webhook_url; + + $this->deploymentSuccessDiscordNotifications = $this->settings->deployment_success_discord_notifications; + $this->deploymentFailureDiscordNotifications = $this->settings->deployment_failure_discord_notifications; + $this->statusChangeDiscordNotifications = $this->settings->status_change_discord_notifications; + $this->backupSuccessDiscordNotifications = $this->settings->backup_success_discord_notifications; + $this->backupFailureDiscordNotifications = $this->settings->backup_failure_discord_notifications; + $this->scheduledTaskSuccessDiscordNotifications = $this->settings->scheduled_task_success_discord_notifications; + $this->scheduledTaskFailureDiscordNotifications = $this->settings->scheduled_task_failure_discord_notifications; + $this->dockerCleanupSuccessDiscordNotifications = $this->settings->docker_cleanup_success_discord_notifications; + $this->dockerCleanupFailureDiscordNotifications = $this->settings->docker_cleanup_failure_discord_notifications; + $this->serverDiskUsageDiscordNotifications = $this->settings->server_disk_usage_discord_notifications; + $this->serverReachableDiscordNotifications = $this->settings->server_reachable_discord_notifications; + $this->serverUnreachableDiscordNotifications = $this->settings->server_unreachable_discord_notifications; } } @@ -117,7 +154,7 @@ public function saveModel() public function sendTestNotification() { try { - $this->team->notify(new Test); + $this->team->notify(new Test(channel: 'discord')); $this->dispatch('success', 'Test notification sent.'); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Livewire/Notifications/Email.php b/app/Livewire/Notifications/Email.php index c4e4aae146..eac48cf779 100644 --- a/app/Livewire/Notifications/Email.php +++ b/app/Livewire/Notifications/Email.php @@ -2,6 +2,7 @@ namespace App\Livewire\Notifications; +use App\Models\EmailNotificationSettings; use App\Models\Team; use App\Notifications\Test; use Illuminate\Support\Facades\RateLimiter; @@ -11,17 +12,20 @@ class Email extends Component { + protected $listeners = ['refresh' => '$refresh']; + + #[Locked] public Team $team; + #[Locked] + public EmailNotificationSettings $settings; + #[Locked] public string $emails; #[Validate(['boolean'])] public bool $smtpEnabled = false; - #[Validate(['boolean'])] - public bool $useInstanceEmailSettings = false; - #[Validate(['nullable', 'email'])] public ?string $smtpFromAddress = null; @@ -34,11 +38,11 @@ class Email extends Component #[Validate(['nullable', 'string'])] public ?string $smtpHost = null; - #[Validate(['nullable', 'numeric'])] + #[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])] public ?int $smtpPort = null; - #[Validate(['nullable', 'string'])] - public ?string $smtpEncryption = null; + #[Validate(['nullable', 'string', 'in:tls,ssl,none'])] + public ?string $smtpEncryption = 'tls'; #[Validate(['nullable', 'string'])] public ?string $smtpUsername = null; @@ -50,28 +54,49 @@ class Email extends Component public ?int $smtpTimeout = null; #[Validate(['boolean'])] - public bool $smtpNotificationsTest = false; + public bool $resendEnabled = false; + + #[Validate(['nullable', 'string'])] + public ?string $resendApiKey = null; #[Validate(['boolean'])] - public bool $smtpNotificationsDeployments = false; + public bool $useInstanceEmailSettings = false; #[Validate(['boolean'])] - public bool $smtpNotificationsStatusChanges = false; + public bool $deploymentSuccessEmailNotifications = false; #[Validate(['boolean'])] - public bool $smtpNotificationsDatabaseBackups = false; + public bool $deploymentFailureEmailNotifications = true; #[Validate(['boolean'])] - public bool $smtpNotificationsScheduledTasks = false; + public bool $statusChangeEmailNotifications = false; #[Validate(['boolean'])] - public bool $smtpNotificationsServerDiskUsage = false; + public bool $backupSuccessEmailNotifications = false; #[Validate(['boolean'])] - public bool $resendEnabled; + public bool $backupFailureEmailNotifications = true; - #[Validate(['nullable', 'string'])] - public ?string $resendApiKey = null; + #[Validate(['boolean'])] + public bool $scheduledTaskSuccessEmailNotifications = false; + + #[Validate(['boolean'])] + public bool $scheduledTaskFailureEmailNotifications = true; + + #[Validate(['boolean'])] + public bool $dockerCleanupSuccessEmailNotifications = false; + + #[Validate(['boolean'])] + public bool $dockerCleanupFailureEmailNotifications = true; + + #[Validate(['boolean'])] + public bool $serverDiskUsageEmailNotifications = true; + + #[Validate(['boolean'])] + public bool $serverReachableEmailNotifications = false; + + #[Validate(['boolean'])] + public bool $serverUnreachableEmailNotifications = true; #[Validate(['nullable', 'email'])] public ?string $testEmailAddress = null; @@ -81,7 +106,9 @@ public function mount() try { $this->team = auth()->user()->currentTeam(); $this->emails = auth()->user()->email; + $this->settings = $this->team->emailNotificationSettings; $this->syncData(); + $this->testEmailAddress = auth()->user()->email; } catch (\Throwable $e) { return handleError($e, $this); } @@ -91,137 +118,217 @@ public function syncData(bool $toModel = false) { if ($toModel) { $this->validate(); - $this->team->smtp_enabled = $this->smtpEnabled; - $this->team->smtp_from_address = $this->smtpFromAddress; - $this->team->smtp_from_name = $this->smtpFromName; - $this->team->smtp_host = $this->smtpHost; - $this->team->smtp_port = $this->smtpPort; - $this->team->smtp_encryption = $this->smtpEncryption; - $this->team->smtp_username = $this->smtpUsername; - $this->team->smtp_password = $this->smtpPassword; - $this->team->smtp_timeout = $this->smtpTimeout; - $this->team->smtp_recipients = $this->smtpRecipients; - $this->team->smtp_notifications_test = $this->smtpNotificationsTest; - $this->team->smtp_notifications_deployments = $this->smtpNotificationsDeployments; - $this->team->smtp_notifications_status_changes = $this->smtpNotificationsStatusChanges; - $this->team->smtp_notifications_database_backups = $this->smtpNotificationsDatabaseBackups; - $this->team->smtp_notifications_scheduled_tasks = $this->smtpNotificationsScheduledTasks; - $this->team->smtp_notifications_server_disk_usage = $this->smtpNotificationsServerDiskUsage; - $this->team->use_instance_email_settings = $this->useInstanceEmailSettings; - $this->team->resend_enabled = $this->resendEnabled; - $this->team->resend_api_key = $this->resendApiKey; - $this->team->save(); - refreshSession(); + $this->settings->smtp_enabled = $this->smtpEnabled; + $this->settings->smtp_from_address = $this->smtpFromAddress; + $this->settings->smtp_from_name = $this->smtpFromName; + $this->settings->smtp_recipients = $this->smtpRecipients; + $this->settings->smtp_host = $this->smtpHost; + $this->settings->smtp_port = $this->smtpPort; + $this->settings->smtp_encryption = $this->smtpEncryption; + $this->settings->smtp_username = $this->smtpUsername; + $this->settings->smtp_password = $this->smtpPassword; + $this->settings->smtp_timeout = $this->smtpTimeout; + + $this->settings->resend_enabled = $this->resendEnabled; + $this->settings->resend_api_key = $this->resendApiKey; + + $this->settings->use_instance_email_settings = $this->useInstanceEmailSettings; + + $this->settings->deployment_success_email_notifications = $this->deploymentSuccessEmailNotifications; + $this->settings->deployment_failure_email_notifications = $this->deploymentFailureEmailNotifications; + $this->settings->status_change_email_notifications = $this->statusChangeEmailNotifications; + $this->settings->backup_success_email_notifications = $this->backupSuccessEmailNotifications; + $this->settings->backup_failure_email_notifications = $this->backupFailureEmailNotifications; + $this->settings->scheduled_task_success_email_notifications = $this->scheduledTaskSuccessEmailNotifications; + $this->settings->scheduled_task_failure_email_notifications = $this->scheduledTaskFailureEmailNotifications; + $this->settings->docker_cleanup_success_email_notifications = $this->dockerCleanupSuccessEmailNotifications; + $this->settings->docker_cleanup_failure_email_notifications = $this->dockerCleanupFailureEmailNotifications; + $this->settings->server_disk_usage_email_notifications = $this->serverDiskUsageEmailNotifications; + $this->settings->server_reachable_email_notifications = $this->serverReachableEmailNotifications; + $this->settings->server_unreachable_email_notifications = $this->serverUnreachableEmailNotifications; + $this->settings->save(); + } else { - $this->smtpEnabled = $this->team->smtp_enabled; - $this->smtpFromAddress = $this->team->smtp_from_address; - $this->smtpFromName = $this->team->smtp_from_name; - $this->smtpHost = $this->team->smtp_host; - $this->smtpPort = $this->team->smtp_port; - $this->smtpEncryption = $this->team->smtp_encryption; - $this->smtpUsername = $this->team->smtp_username; - $this->smtpPassword = $this->team->smtp_password; - $this->smtpTimeout = $this->team->smtp_timeout; - $this->smtpRecipients = $this->team->smtp_recipients; - $this->smtpNotificationsTest = $this->team->smtp_notifications_test; - $this->smtpNotificationsDeployments = $this->team->smtp_notifications_deployments; - $this->smtpNotificationsStatusChanges = $this->team->smtp_notifications_status_changes; - $this->smtpNotificationsDatabaseBackups = $this->team->smtp_notifications_database_backups; - $this->smtpNotificationsScheduledTasks = $this->team->smtp_notifications_scheduled_tasks; - $this->smtpNotificationsServerDiskUsage = $this->team->smtp_notifications_server_disk_usage; - $this->useInstanceEmailSettings = $this->team->use_instance_email_settings; - $this->resendEnabled = $this->team->resend_enabled; - $this->resendApiKey = $this->team->resend_api_key; + $this->smtpEnabled = $this->settings->smtp_enabled; + $this->smtpFromAddress = $this->settings->smtp_from_address; + $this->smtpFromName = $this->settings->smtp_from_name; + $this->smtpRecipients = $this->settings->smtp_recipients; + $this->smtpHost = $this->settings->smtp_host; + $this->smtpPort = $this->settings->smtp_port; + $this->smtpEncryption = $this->settings->smtp_encryption; + $this->smtpUsername = $this->settings->smtp_username; + $this->smtpPassword = $this->settings->smtp_password; + $this->smtpTimeout = $this->settings->smtp_timeout; + + $this->resendEnabled = $this->settings->resend_enabled; + $this->resendApiKey = $this->settings->resend_api_key; + + $this->useInstanceEmailSettings = $this->settings->use_instance_email_settings; + + $this->deploymentSuccessEmailNotifications = $this->settings->deployment_success_email_notifications; + $this->deploymentFailureEmailNotifications = $this->settings->deployment_failure_email_notifications; + $this->statusChangeEmailNotifications = $this->settings->status_change_email_notifications; + $this->backupSuccessEmailNotifications = $this->settings->backup_success_email_notifications; + $this->backupFailureEmailNotifications = $this->settings->backup_failure_email_notifications; + $this->scheduledTaskSuccessEmailNotifications = $this->settings->scheduled_task_success_email_notifications; + $this->scheduledTaskFailureEmailNotifications = $this->settings->scheduled_task_failure_email_notifications; + $this->dockerCleanupSuccessEmailNotifications = $this->settings->docker_cleanup_success_email_notifications; + $this->dockerCleanupFailureEmailNotifications = $this->settings->docker_cleanup_failure_email_notifications; + $this->serverDiskUsageEmailNotifications = $this->settings->server_disk_usage_email_notifications; + $this->serverReachableEmailNotifications = $this->settings->server_reachable_email_notifications; + $this->serverUnreachableEmailNotifications = $this->settings->server_unreachable_email_notifications; } } - public function sendTestEmail() + public function submit() { 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->testEmailAddress)); - $this->dispatch('success', 'Test Email sent.'); - }, - $decaySeconds = 10, - ); - - if (! $executed) { - throw new \Exception('Too many messages sent!'); - } + $this->resetErrorBag(); + $this->saveModel(); } catch (\Throwable $e) { return handleError($e, $this); } } - public function instantSaveInstance() + public function saveModel() + { + $this->syncData(true); + $this->dispatch('success', 'Email notifications settings updated.'); + } + + public function instantSave(?string $type = null) { try { - $this->smtpEnabled = false; - $this->resendEnabled = false; - $this->saveModel(); + $this->resetErrorBag(); + + if ($type === 'SMTP') { + $this->submitSmtp(); + } elseif ($type === 'Resend') { + $this->submitResend(); + } else { + $this->smtpEnabled = false; + $this->resendEnabled = false; + $this->saveModel(); + + return; + } } catch (\Throwable $e) { + if ($type === 'SMTP') { + $this->smtpEnabled = false; + } elseif ($type === 'Resend') { + $this->resendEnabled = false; + } + return handleError($e, $this); + } finally { + $this->dispatch('refresh'); } } - public function instantSaveSmtpEnabled() + public function submitSmtp() { try { + $this->resetErrorBag(); $this->validate([ - 'smtpHost' => 'required', + 'smtpEnabled' => 'boolean', + 'smtpFromAddress' => 'required|email', + 'smtpFromName' => 'required|string', + 'smtpHost' => 'required|string', 'smtpPort' => 'required|numeric', + 'smtpEncryption' => 'required|string|in:tls,ssl,none', + 'smtpUsername' => 'nullable|string', + 'smtpPassword' => 'nullable|string', + 'smtpTimeout' => 'nullable|numeric', ], [ + 'smtpFromAddress.required' => 'From Address is required.', + 'smtpFromAddress.email' => 'Please enter a valid email address.', + 'smtpFromName.required' => 'From Name is required.', 'smtpHost.required' => 'SMTP Host is required.', 'smtpPort.required' => 'SMTP Port is required.', + 'smtpPort.numeric' => 'SMTP Port must be a number.', + 'smtpEncryption.required' => 'Encryption type is required.', ]); + + $this->settings->resend_enabled = false; + $this->settings->use_instance_email_settings = false; $this->resendEnabled = false; - $this->saveModel(); + $this->useInstanceEmailSettings = false; + + $this->settings->smtp_enabled = $this->smtpEnabled; + $this->settings->smtp_from_address = $this->smtpFromAddress; + $this->settings->smtp_from_name = $this->smtpFromName; + $this->settings->smtp_host = $this->smtpHost; + $this->settings->smtp_port = $this->smtpPort; + $this->settings->smtp_encryption = $this->smtpEncryption; + $this->settings->smtp_username = $this->smtpUsername; + $this->settings->smtp_password = $this->smtpPassword; + $this->settings->smtp_timeout = $this->smtpTimeout; + + $this->settings->save(); + $this->dispatch('success', 'SMTP settings updated.'); } catch (\Throwable $e) { $this->smtpEnabled = false; - return handleError($e, $this); + return handleError($e); } } - public function instantSaveResend() + public function submitResend() { try { + $this->resetErrorBag(); $this->validate([ - 'resendApiKey' => 'required', + 'resendEnabled' => 'boolean', + 'resendApiKey' => 'required|string', + 'smtpFromAddress' => 'required|email', + 'smtpFromName' => 'required|string', ], [ 'resendApiKey.required' => 'Resend API Key is required.', + 'smtpFromAddress.required' => 'From Address is required.', + 'smtpFromAddress.email' => 'Please enter a valid email address.', + 'smtpFromName.required' => 'From Name is required.', ]); + + $this->settings->smtp_enabled = false; + $this->settings->use_instance_email_settings = false; $this->smtpEnabled = false; - $this->saveModel(); - } catch (\Throwable $e) { - $this->resendEnabled = false; + $this->useInstanceEmailSettings = false; + + $this->settings->resend_enabled = $this->resendEnabled; + $this->settings->resend_api_key = $this->resendApiKey; + $this->settings->smtp_from_address = $this->smtpFromAddress; + $this->settings->smtp_from_name = $this->smtpFromName; + $this->settings->save(); + $this->dispatch('success', 'Resend settings updated.'); + } catch (\Throwable $e) { return handleError($e, $this); } } - public function saveModel() - { - $this->syncData(true); - refreshSession(); - $this->dispatch('success', 'Settings saved.'); - } - - public function submit() + public function sendTestEmail() { try { - $this->resetErrorBag(); - $this->saveModel(); + $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->testEmailAddress, 'email')); + $this->dispatch('success', 'Test Email sent.'); + }, + $decaySeconds = 10, + ); + + if (! $executed) { + throw new \Exception('Too many messages sent!'); + } } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Notifications/Pushover.php b/app/Livewire/Notifications/Pushover.php new file mode 100644 index 0000000000..f1e4c464da --- /dev/null +++ b/app/Livewire/Notifications/Pushover.php @@ -0,0 +1,184 @@ + '$refresh']; + + #[Locked] + public Team $team; + + #[Locked] + public PushoverNotificationSettings $settings; + + #[Validate(['boolean'])] + public bool $pushoverEnabled = false; + + #[Validate(['nullable', 'string'])] + public ?string $pushoverUserKey = null; + + #[Validate(['nullable', 'string'])] + public ?string $pushoverApiToken = null; + + #[Validate(['boolean'])] + public bool $deploymentSuccessPushoverNotifications = false; + + #[Validate(['boolean'])] + public bool $deploymentFailurePushoverNotifications = true; + + #[Validate(['boolean'])] + public bool $statusChangePushoverNotifications = false; + + #[Validate(['boolean'])] + public bool $backupSuccessPushoverNotifications = false; + + #[Validate(['boolean'])] + public bool $backupFailurePushoverNotifications = true; + + #[Validate(['boolean'])] + public bool $scheduledTaskSuccessPushoverNotifications = false; + + #[Validate(['boolean'])] + public bool $scheduledTaskFailurePushoverNotifications = true; + + #[Validate(['boolean'])] + public bool $dockerCleanupSuccessPushoverNotifications = false; + + #[Validate(['boolean'])] + public bool $dockerCleanupFailurePushoverNotifications = true; + + #[Validate(['boolean'])] + public bool $serverDiskUsagePushoverNotifications = true; + + #[Validate(['boolean'])] + public bool $serverReachablePushoverNotifications = false; + + #[Validate(['boolean'])] + public bool $serverUnreachablePushoverNotifications = true; + + public function mount() + { + try { + $this->team = auth()->user()->currentTeam(); + $this->settings = $this->team->pushoverNotificationSettings; + $this->syncData(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function syncData(bool $toModel = false) + { + if ($toModel) { + $this->validate(); + $this->settings->pushover_enabled = $this->pushoverEnabled; + $this->settings->pushover_user_key = $this->pushoverUserKey; + $this->settings->pushover_api_token = $this->pushoverApiToken; + + $this->settings->deployment_success_pushover_notifications = $this->deploymentSuccessPushoverNotifications; + $this->settings->deployment_failure_pushover_notifications = $this->deploymentFailurePushoverNotifications; + $this->settings->status_change_pushover_notifications = $this->statusChangePushoverNotifications; + $this->settings->backup_success_pushover_notifications = $this->backupSuccessPushoverNotifications; + $this->settings->backup_failure_pushover_notifications = $this->backupFailurePushoverNotifications; + $this->settings->scheduled_task_success_pushover_notifications = $this->scheduledTaskSuccessPushoverNotifications; + $this->settings->scheduled_task_failure_pushover_notifications = $this->scheduledTaskFailurePushoverNotifications; + $this->settings->docker_cleanup_success_pushover_notifications = $this->dockerCleanupSuccessPushoverNotifications; + $this->settings->docker_cleanup_failure_pushover_notifications = $this->dockerCleanupFailurePushoverNotifications; + $this->settings->server_disk_usage_pushover_notifications = $this->serverDiskUsagePushoverNotifications; + $this->settings->server_reachable_pushover_notifications = $this->serverReachablePushoverNotifications; + $this->settings->server_unreachable_pushover_notifications = $this->serverUnreachablePushoverNotifications; + + $this->settings->save(); + refreshSession(); + } else { + $this->pushoverEnabled = $this->settings->pushover_enabled; + $this->pushoverUserKey = $this->settings->pushover_user_key; + $this->pushoverApiToken = $this->settings->pushover_api_token; + + $this->deploymentSuccessPushoverNotifications = $this->settings->deployment_success_pushover_notifications; + $this->deploymentFailurePushoverNotifications = $this->settings->deployment_failure_pushover_notifications; + $this->statusChangePushoverNotifications = $this->settings->status_change_pushover_notifications; + $this->backupSuccessPushoverNotifications = $this->settings->backup_success_pushover_notifications; + $this->backupFailurePushoverNotifications = $this->settings->backup_failure_pushover_notifications; + $this->scheduledTaskSuccessPushoverNotifications = $this->settings->scheduled_task_success_pushover_notifications; + $this->scheduledTaskFailurePushoverNotifications = $this->settings->scheduled_task_failure_pushover_notifications; + $this->dockerCleanupSuccessPushoverNotifications = $this->settings->docker_cleanup_success_pushover_notifications; + $this->dockerCleanupFailurePushoverNotifications = $this->settings->docker_cleanup_failure_pushover_notifications; + $this->serverDiskUsagePushoverNotifications = $this->settings->server_disk_usage_pushover_notifications; + $this->serverReachablePushoverNotifications = $this->settings->server_reachable_pushover_notifications; + $this->serverUnreachablePushoverNotifications = $this->settings->server_unreachable_pushover_notifications; + } + } + + public function instantSavePushoverEnabled() + { + try { + $this->validate([ + 'pushoverUserKey' => 'required', + 'pushoverApiToken' => 'required', + ], [ + 'pushoverUserKey.required' => 'Pushover User Key is required.', + 'pushoverApiToken.required' => 'Pushover API Token is required.', + ]); + $this->saveModel(); + } catch (\Throwable $e) { + $this->pushoverEnabled = false; + + return handleError($e, $this); + } finally { + $this->dispatch('refresh'); + } + } + + public function instantSave() + { + try { + $this->syncData(true); + } catch (\Throwable $e) { + return handleError($e, $this); + } finally { + $this->dispatch('refresh'); + } + } + + public function submit() + { + try { + $this->resetErrorBag(); + $this->syncData(true); + $this->saveModel(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function saveModel() + { + $this->syncData(true); + refreshSession(); + $this->dispatch('success', 'Settings saved.'); + } + + public function sendTestNotification() + { + try { + $this->team->notify(new Test(channel: 'pushover')); + $this->dispatch('success', 'Test notification sent.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function render() + { + return view('livewire.notifications.pushover'); + } +} diff --git a/app/Livewire/Notifications/Slack.php b/app/Livewire/Notifications/Slack.php new file mode 100644 index 0000000000..f47ad8a979 --- /dev/null +++ b/app/Livewire/Notifications/Slack.php @@ -0,0 +1,177 @@ + '$refresh']; + + #[Locked] + public Team $team; + + #[Locked] + public SlackNotificationSettings $settings; + + #[Validate(['boolean'])] + public bool $slackEnabled = false; + + #[Validate(['url', 'nullable'])] + public ?string $slackWebhookUrl = null; + + #[Validate(['boolean'])] + public bool $deploymentSuccessSlackNotifications = false; + + #[Validate(['boolean'])] + public bool $deploymentFailureSlackNotifications = true; + + #[Validate(['boolean'])] + public bool $statusChangeSlackNotifications = false; + + #[Validate(['boolean'])] + public bool $backupSuccessSlackNotifications = false; + + #[Validate(['boolean'])] + public bool $backupFailureSlackNotifications = true; + + #[Validate(['boolean'])] + public bool $scheduledTaskSuccessSlackNotifications = false; + + #[Validate(['boolean'])] + public bool $scheduledTaskFailureSlackNotifications = true; + + #[Validate(['boolean'])] + public bool $dockerCleanupSuccessSlackNotifications = false; + + #[Validate(['boolean'])] + public bool $dockerCleanupFailureSlackNotifications = true; + + #[Validate(['boolean'])] + public bool $serverDiskUsageSlackNotifications = true; + + #[Validate(['boolean'])] + public bool $serverReachableSlackNotifications = false; + + #[Validate(['boolean'])] + public bool $serverUnreachableSlackNotifications = true; + + public function mount() + { + try { + $this->team = auth()->user()->currentTeam(); + $this->settings = $this->team->slackNotificationSettings; + $this->syncData(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function syncData(bool $toModel = false) + { + if ($toModel) { + $this->validate(); + $this->settings->slack_enabled = $this->slackEnabled; + $this->settings->slack_webhook_url = $this->slackWebhookUrl; + + $this->settings->deployment_success_slack_notifications = $this->deploymentSuccessSlackNotifications; + $this->settings->deployment_failure_slack_notifications = $this->deploymentFailureSlackNotifications; + $this->settings->status_change_slack_notifications = $this->statusChangeSlackNotifications; + $this->settings->backup_success_slack_notifications = $this->backupSuccessSlackNotifications; + $this->settings->backup_failure_slack_notifications = $this->backupFailureSlackNotifications; + $this->settings->scheduled_task_success_slack_notifications = $this->scheduledTaskSuccessSlackNotifications; + $this->settings->scheduled_task_failure_slack_notifications = $this->scheduledTaskFailureSlackNotifications; + $this->settings->docker_cleanup_success_slack_notifications = $this->dockerCleanupSuccessSlackNotifications; + $this->settings->docker_cleanup_failure_slack_notifications = $this->dockerCleanupFailureSlackNotifications; + $this->settings->server_disk_usage_slack_notifications = $this->serverDiskUsageSlackNotifications; + $this->settings->server_reachable_slack_notifications = $this->serverReachableSlackNotifications; + $this->settings->server_unreachable_slack_notifications = $this->serverUnreachableSlackNotifications; + + $this->settings->save(); + refreshSession(); + } else { + $this->slackEnabled = $this->settings->slack_enabled; + $this->slackWebhookUrl = $this->settings->slack_webhook_url; + + $this->deploymentSuccessSlackNotifications = $this->settings->deployment_success_slack_notifications; + $this->deploymentFailureSlackNotifications = $this->settings->deployment_failure_slack_notifications; + $this->statusChangeSlackNotifications = $this->settings->status_change_slack_notifications; + $this->backupSuccessSlackNotifications = $this->settings->backup_success_slack_notifications; + $this->backupFailureSlackNotifications = $this->settings->backup_failure_slack_notifications; + $this->scheduledTaskSuccessSlackNotifications = $this->settings->scheduled_task_success_slack_notifications; + $this->scheduledTaskFailureSlackNotifications = $this->settings->scheduled_task_failure_slack_notifications; + $this->dockerCleanupSuccessSlackNotifications = $this->settings->docker_cleanup_success_slack_notifications; + $this->dockerCleanupFailureSlackNotifications = $this->settings->docker_cleanup_failure_slack_notifications; + $this->serverDiskUsageSlackNotifications = $this->settings->server_disk_usage_slack_notifications; + $this->serverReachableSlackNotifications = $this->settings->server_reachable_slack_notifications; + $this->serverUnreachableSlackNotifications = $this->settings->server_unreachable_slack_notifications; + } + } + + public function instantSaveSlackEnabled() + { + try { + $this->validate([ + 'slackWebhookUrl' => 'required', + ], [ + 'slackWebhookUrl.required' => 'Slack Webhook URL is required.', + ]); + $this->saveModel(); + } catch (\Throwable $e) { + $this->slackEnabled = false; + + return handleError($e, $this); + } finally { + $this->dispatch('refresh'); + } + } + + public function instantSave() + { + try { + $this->syncData(true); + } catch (\Throwable $e) { + return handleError($e, $this); + } finally { + $this->dispatch('refresh'); + } + } + + public function submit() + { + try { + $this->resetErrorBag(); + $this->syncData(true); + $this->saveModel(); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function saveModel() + { + $this->syncData(true); + refreshSession(); + $this->dispatch('success', 'Settings saved.'); + } + + public function sendTestNotification() + { + try { + $this->team->notify(new Test(channel: 'slack')); + $this->dispatch('success', 'Test notification sent.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + + public function render() + { + return view('livewire.notifications.slack'); + } +} diff --git a/app/Livewire/Notifications/Telegram.php b/app/Livewire/Notifications/Telegram.php index 15ec205778..01afb4ac67 100644 --- a/app/Livewire/Notifications/Telegram.php +++ b/app/Livewire/Notifications/Telegram.php @@ -3,14 +3,22 @@ namespace App\Livewire\Notifications; use App\Models\Team; +use App\Models\TelegramNotificationSettings; use App\Notifications\Test; +use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; use Livewire\Component; class Telegram extends Component { + protected $listeners = ['refresh' => '$refresh']; + + #[Locked] public Team $team; + #[Locked] + public TelegramNotificationSettings $settings; + #[Validate(['boolean'])] public bool $telegramEnabled = false; @@ -21,42 +29,82 @@ class Telegram extends Component public ?string $telegramChatId = null; #[Validate(['boolean'])] - public bool $telegramNotificationsTest = false; + public bool $deploymentSuccessTelegramNotifications = false; + + #[Validate(['boolean'])] + public bool $deploymentFailureTelegramNotifications = true; + + #[Validate(['boolean'])] + public bool $statusChangeTelegramNotifications = false; + + #[Validate(['boolean'])] + public bool $backupSuccessTelegramNotifications = false; + + #[Validate(['boolean'])] + public bool $backupFailureTelegramNotifications = true; + + #[Validate(['boolean'])] + public bool $scheduledTaskSuccessTelegramNotifications = false; + + #[Validate(['boolean'])] + public bool $scheduledTaskFailureTelegramNotifications = true; + + #[Validate(['boolean'])] + public bool $dockerCleanupSuccessTelegramNotifications = false; #[Validate(['boolean'])] - public bool $telegramNotificationsDeployments = false; + public bool $dockerCleanupFailureTelegramNotifications = true; #[Validate(['boolean'])] - public bool $telegramNotificationsStatusChanges = false; + public bool $serverDiskUsageTelegramNotifications = true; #[Validate(['boolean'])] - public bool $telegramNotificationsDatabaseBackups = false; + public bool $serverReachableTelegramNotifications = false; #[Validate(['boolean'])] - public bool $telegramNotificationsScheduledTasks = false; + public bool $serverUnreachableTelegramNotifications = true; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsTestMessageThreadId = null; + public ?string $telegramNotificationsDeploymentSuccessThreadId = null; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsDeploymentsMessageThreadId = null; + public ?string $telegramNotificationsDeploymentFailureThreadId = null; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsStatusChangesMessageThreadId = null; + public ?string $telegramNotificationsStatusChangeThreadId = null; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsDatabaseBackupsMessageThreadId = null; + public ?string $telegramNotificationsBackupSuccessThreadId = null; #[Validate(['nullable', 'string'])] - public ?string $telegramNotificationsScheduledTasksThreadId = null; + public ?string $telegramNotificationsBackupFailureThreadId = null; - #[Validate(['boolean'])] - public bool $telegramNotificationsServerDiskUsage = false; + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsScheduledTaskSuccessThreadId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsScheduledTaskFailureThreadId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsDockerCleanupSuccessThreadId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsDockerCleanupFailureThreadId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsServerDiskUsageThreadId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsServerReachableThreadId = null; + + #[Validate(['nullable', 'string'])] + public ?string $telegramNotificationsServerUnreachableThreadId = null; public function mount() { try { $this->team = auth()->user()->currentTeam(); + $this->settings = $this->team->telegramNotificationSettings; $this->syncData(); } catch (\Throwable $e) { return handleError($e, $this); @@ -67,39 +115,68 @@ public function syncData(bool $toModel = false) { if ($toModel) { $this->validate(); - $this->team->telegram_enabled = $this->telegramEnabled; - $this->team->telegram_token = $this->telegramToken; - $this->team->telegram_chat_id = $this->telegramChatId; - $this->team->telegram_notifications_test = $this->telegramNotificationsTest; - $this->team->telegram_notifications_deployments = $this->telegramNotificationsDeployments; - $this->team->telegram_notifications_status_changes = $this->telegramNotificationsStatusChanges; - $this->team->telegram_notifications_database_backups = $this->telegramNotificationsDatabaseBackups; - $this->team->telegram_notifications_scheduled_tasks = $this->telegramNotificationsScheduledTasks; - $this->team->telegram_notifications_test_message_thread_id = $this->telegramNotificationsTestMessageThreadId; - $this->team->telegram_notifications_deployments_message_thread_id = $this->telegramNotificationsDeploymentsMessageThreadId; - $this->team->telegram_notifications_status_changes_message_thread_id = $this->telegramNotificationsStatusChangesMessageThreadId; - $this->team->telegram_notifications_database_backups_message_thread_id = $this->telegramNotificationsDatabaseBackupsMessageThreadId; - $this->team->telegram_notifications_scheduled_tasks_thread_id = $this->telegramNotificationsScheduledTasksThreadId; - $this->team->telegram_notifications_server_disk_usage = $this->telegramNotificationsServerDiskUsage; - $this->team->save(); - refreshSession(); + $this->settings->telegram_enabled = $this->telegramEnabled; + $this->settings->telegram_token = $this->telegramToken; + $this->settings->telegram_chat_id = $this->telegramChatId; + + $this->settings->deployment_success_telegram_notifications = $this->deploymentSuccessTelegramNotifications; + $this->settings->deployment_failure_telegram_notifications = $this->deploymentFailureTelegramNotifications; + $this->settings->status_change_telegram_notifications = $this->statusChangeTelegramNotifications; + $this->settings->backup_success_telegram_notifications = $this->backupSuccessTelegramNotifications; + $this->settings->backup_failure_telegram_notifications = $this->backupFailureTelegramNotifications; + $this->settings->scheduled_task_success_telegram_notifications = $this->scheduledTaskSuccessTelegramNotifications; + $this->settings->scheduled_task_failure_telegram_notifications = $this->scheduledTaskFailureTelegramNotifications; + $this->settings->docker_cleanup_success_telegram_notifications = $this->dockerCleanupSuccessTelegramNotifications; + $this->settings->docker_cleanup_failure_telegram_notifications = $this->dockerCleanupFailureTelegramNotifications; + $this->settings->server_disk_usage_telegram_notifications = $this->serverDiskUsageTelegramNotifications; + $this->settings->server_reachable_telegram_notifications = $this->serverReachableTelegramNotifications; + $this->settings->server_unreachable_telegram_notifications = $this->serverUnreachableTelegramNotifications; + + $this->settings->telegram_notifications_deployment_success_thread_id = $this->telegramNotificationsDeploymentSuccessThreadId; + $this->settings->telegram_notifications_deployment_failure_thread_id = $this->telegramNotificationsDeploymentFailureThreadId; + $this->settings->telegram_notifications_status_change_thread_id = $this->telegramNotificationsStatusChangeThreadId; + $this->settings->telegram_notifications_backup_success_thread_id = $this->telegramNotificationsBackupSuccessThreadId; + $this->settings->telegram_notifications_backup_failure_thread_id = $this->telegramNotificationsBackupFailureThreadId; + $this->settings->telegram_notifications_scheduled_task_success_thread_id = $this->telegramNotificationsScheduledTaskSuccessThreadId; + $this->settings->telegram_notifications_scheduled_task_failure_thread_id = $this->telegramNotificationsScheduledTaskFailureThreadId; + $this->settings->telegram_notifications_docker_cleanup_success_thread_id = $this->telegramNotificationsDockerCleanupSuccessThreadId; + $this->settings->telegram_notifications_docker_cleanup_failure_thread_id = $this->telegramNotificationsDockerCleanupFailureThreadId; + $this->settings->telegram_notifications_server_disk_usage_thread_id = $this->telegramNotificationsServerDiskUsageThreadId; + $this->settings->telegram_notifications_server_reachable_thread_id = $this->telegramNotificationsServerReachableThreadId; + $this->settings->telegram_notifications_server_unreachable_thread_id = $this->telegramNotificationsServerUnreachableThreadId; + + $this->settings->save(); } else { - $this->telegramEnabled = $this->team->telegram_enabled; - $this->telegramToken = $this->team->telegram_token; - $this->telegramChatId = $this->team->telegram_chat_id; - $this->telegramNotificationsTest = $this->team->telegram_notifications_test; - $this->telegramNotificationsDeployments = $this->team->telegram_notifications_deployments; - $this->telegramNotificationsStatusChanges = $this->team->telegram_notifications_status_changes; - $this->telegramNotificationsDatabaseBackups = $this->team->telegram_notifications_database_backups; - $this->telegramNotificationsScheduledTasks = $this->team->telegram_notifications_scheduled_tasks; - $this->telegramNotificationsTestMessageThreadId = $this->team->telegram_notifications_test_message_thread_id; - $this->telegramNotificationsDeploymentsMessageThreadId = $this->team->telegram_notifications_deployments_message_thread_id; - $this->telegramNotificationsStatusChangesMessageThreadId = $this->team->telegram_notifications_status_changes_message_thread_id; - $this->telegramNotificationsDatabaseBackupsMessageThreadId = $this->team->telegram_notifications_database_backups_message_thread_id; - $this->telegramNotificationsScheduledTasksThreadId = $this->team->telegram_notifications_scheduled_tasks_thread_id; - $this->telegramNotificationsServerDiskUsage = $this->team->telegram_notifications_server_disk_usage; + $this->telegramEnabled = $this->settings->telegram_enabled; + $this->telegramToken = $this->settings->telegram_token; + $this->telegramChatId = $this->settings->telegram_chat_id; + + $this->deploymentSuccessTelegramNotifications = $this->settings->deployment_success_telegram_notifications; + $this->deploymentFailureTelegramNotifications = $this->settings->deployment_failure_telegram_notifications; + $this->statusChangeTelegramNotifications = $this->settings->status_change_telegram_notifications; + $this->backupSuccessTelegramNotifications = $this->settings->backup_success_telegram_notifications; + $this->backupFailureTelegramNotifications = $this->settings->backup_failure_telegram_notifications; + $this->scheduledTaskSuccessTelegramNotifications = $this->settings->scheduled_task_success_telegram_notifications; + $this->scheduledTaskFailureTelegramNotifications = $this->settings->scheduled_task_failure_telegram_notifications; + $this->dockerCleanupSuccessTelegramNotifications = $this->settings->docker_cleanup_success_telegram_notifications; + $this->dockerCleanupFailureTelegramNotifications = $this->settings->docker_cleanup_failure_telegram_notifications; + $this->serverDiskUsageTelegramNotifications = $this->settings->server_disk_usage_telegram_notifications; + $this->serverReachableTelegramNotifications = $this->settings->server_reachable_telegram_notifications; + $this->serverUnreachableTelegramNotifications = $this->settings->server_unreachable_telegram_notifications; + + $this->telegramNotificationsDeploymentSuccessThreadId = $this->settings->telegram_notifications_deployment_success_thread_id; + $this->telegramNotificationsDeploymentFailureThreadId = $this->settings->telegram_notifications_deployment_failure_thread_id; + $this->telegramNotificationsStatusChangeThreadId = $this->settings->telegram_notifications_status_change_thread_id; + $this->telegramNotificationsBackupSuccessThreadId = $this->settings->telegram_notifications_backup_success_thread_id; + $this->telegramNotificationsBackupFailureThreadId = $this->settings->telegram_notifications_backup_failure_thread_id; + $this->telegramNotificationsScheduledTaskSuccessThreadId = $this->settings->telegram_notifications_scheduled_task_success_thread_id; + $this->telegramNotificationsScheduledTaskFailureThreadId = $this->settings->telegram_notifications_scheduled_task_failure_thread_id; + $this->telegramNotificationsDockerCleanupSuccessThreadId = $this->settings->telegram_notifications_docker_cleanup_success_thread_id; + $this->telegramNotificationsDockerCleanupFailureThreadId = $this->settings->telegram_notifications_docker_cleanup_failure_thread_id; + $this->telegramNotificationsServerDiskUsageThreadId = $this->settings->telegram_notifications_server_disk_usage_thread_id; + $this->telegramNotificationsServerReachableThreadId = $this->settings->telegram_notifications_server_reachable_thread_id; + $this->telegramNotificationsServerUnreachableThreadId = $this->settings->telegram_notifications_server_unreachable_thread_id; } - } public function instantSave() @@ -108,6 +185,8 @@ public function instantSave() $this->syncData(true); } catch (\Throwable $e) { return handleError($e, $this); + } finally { + $this->dispatch('refresh'); } } @@ -137,6 +216,8 @@ public function instantSaveTelegramEnabled() $this->telegramEnabled = false; return handleError($e, $this); + } finally { + $this->dispatch('refresh'); } } @@ -150,7 +231,7 @@ public function saveModel() public function sendTestNotification() { try { - $this->team->notify(new Test); + $this->team->notify(new Test(channel: 'telegram')); $this->dispatch('success', 'Test notification sent.'); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Livewire/Project/Application/Advanced.php b/app/Livewire/Project/Application/Advanced.php index 05ac25429e..cb63f0e1a6 100644 --- a/app/Livewire/Project/Application/Advanced.php +++ b/app/Livewire/Project/Application/Advanced.php @@ -25,6 +25,9 @@ class Advanced extends Component #[Validate(['boolean'])] public bool $isAutoDeployEnabled = true; + #[Validate(['boolean'])] + public bool $disableBuildCache = false; + #[Validate(['boolean'])] public bool $isLogDrainEnabled = false; @@ -95,6 +98,7 @@ public function syncData(bool $toModel = false) $this->application->settings->is_stripprefix_enabled = $this->isStripprefixEnabled; $this->application->settings->is_raw_compose_deployment_enabled = $this->isRawComposeDeploymentEnabled; $this->application->settings->connect_to_docker_network = $this->isConnectToDockerNetworkEnabled; + $this->application->settings->disable_build_cache = $this->disableBuildCache; $this->application->settings->save(); } else { $this->isForceHttpsEnabled = $this->application->isForceHttpsEnabled(); @@ -116,6 +120,7 @@ public function syncData(bool $toModel = false) $this->customInternalName = $this->application->settings->custom_internal_name; $this->isRawComposeDeploymentEnabled = $this->application->settings->is_raw_compose_deployment_enabled; $this->isConnectToDockerNetworkEnabled = $this->application->settings->connect_to_docker_network; + $this->disableBuildCache = $this->application->settings->disable_build_cache; } } diff --git a/app/Livewire/Project/Resource/Index.php b/app/Livewire/Project/Resource/Index.php index 2834968875..0c2ea802a6 100644 --- a/app/Livewire/Project/Resource/Index.php +++ b/app/Livewire/Project/Resource/Index.php @@ -46,125 +46,84 @@ public function mount() return redirect()->route('dashboard'); } $this->project = $project; - $this->environment = $environment; - $this->applications = $this->environment->applications->load(['tags']); + $this->environment = $environment->loadCount([ + 'applications', + 'redis', + 'postgresqls', + 'mysqls', + 'keydbs', + 'dragonflies', + 'clickhouses', + 'mariadbs', + 'mongodbs', + 'services', + ]); + + // Eager load all relationships for applications including nested ones + $this->applications = $this->environment->applications()->with([ + 'tags', + 'additional_servers.settings', + 'additional_networks', + 'destination.server.settings', + 'settings', + ])->get()->sortBy('name'); $this->applications = $this->applications->map(function ($application) { - if (data_get($application, 'environment.project.uuid')) { - $application->hrefLink = route('project.application.configuration', [ - 'project_uuid' => data_get($application, 'environment.project.uuid'), - 'environment_name' => data_get($application, 'environment.name'), - 'application_uuid' => data_get($application, 'uuid'), - ]); - } + $application->hrefLink = route('project.application.configuration', [ + 'project_uuid' => $this->project->uuid, + 'application_uuid' => $application->uuid, + 'environment_name' => $this->environment->name, + ]); return $application; }); - $this->postgresqls = $this->environment->postgresqls->load(['tags'])->sortBy('name'); - $this->postgresqls = $this->postgresqls->map(function ($postgresql) { - if (data_get($postgresql, 'environment.project.uuid')) { - $postgresql->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($postgresql, 'environment.project.uuid'), - 'environment_name' => data_get($postgresql, 'environment.name'), - 'database_uuid' => data_get($postgresql, 'uuid'), - ]); - } - - return $postgresql; - }); - $this->redis = $this->environment->redis->load(['tags'])->sortBy('name'); - $this->redis = $this->redis->map(function ($redis) { - if (data_get($redis, 'environment.project.uuid')) { - $redis->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($redis, 'environment.project.uuid'), - 'environment_name' => data_get($redis, 'environment.name'), - 'database_uuid' => data_get($redis, 'uuid'), - ]); - } - - return $redis; - }); - $this->mongodbs = $this->environment->mongodbs->load(['tags'])->sortBy('name'); - $this->mongodbs = $this->mongodbs->map(function ($mongodb) { - if (data_get($mongodb, 'environment.project.uuid')) { - $mongodb->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($mongodb, 'environment.project.uuid'), - 'environment_name' => data_get($mongodb, 'environment.name'), - 'database_uuid' => data_get($mongodb, 'uuid'), - ]); - } - - return $mongodb; - }); - $this->mysqls = $this->environment->mysqls->load(['tags'])->sortBy('name'); - $this->mysqls = $this->mysqls->map(function ($mysql) { - if (data_get($mysql, 'environment.project.uuid')) { - $mysql->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($mysql, 'environment.project.uuid'), - 'environment_name' => data_get($mysql, 'environment.name'), - 'database_uuid' => data_get($mysql, 'uuid'), - ]); - } - return $mysql; - }); - $this->mariadbs = $this->environment->mariadbs->load(['tags'])->sortBy('name'); - $this->mariadbs = $this->mariadbs->map(function ($mariadb) { - if (data_get($mariadb, 'environment.project.uuid')) { - $mariadb->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($mariadb, 'environment.project.uuid'), - 'environment_name' => data_get($mariadb, 'environment.name'), - 'database_uuid' => data_get($mariadb, 'uuid'), + // Load all database resources in a single query per type + $databaseTypes = [ + 'postgresqls' => 'postgresqls', + 'redis' => 'redis', + 'mongodbs' => 'mongodbs', + 'mysqls' => 'mysqls', + 'mariadbs' => 'mariadbs', + 'keydbs' => 'keydbs', + 'dragonflies' => 'dragonflies', + 'clickhouses' => 'clickhouses', + ]; + + // Load all server-related data first to prevent duplicate queries + $serverData = $this->environment->applications() + ->with(['destination.server.settings']) + ->get() + ->pluck('destination.server') + ->filter() + ->unique('id'); + + foreach ($databaseTypes as $property => $relation) { + $this->{$property} = $this->environment->{$relation}()->with([ + 'tags', + 'destination.server.settings', + ])->get()->sortBy('name'); + $this->{$property} = $this->{$property}->map(function ($db) { + $db->hrefLink = route('project.database.configuration', [ + 'project_uuid' => $this->project->uuid, + 'database_uuid' => $db->uuid, + 'environment_name' => $this->environment->name, ]); - } - return $mariadb; - }); - $this->keydbs = $this->environment->keydbs->load(['tags'])->sortBy('name'); - $this->keydbs = $this->keydbs->map(function ($keydb) { - if (data_get($keydb, 'environment.project.uuid')) { - $keydb->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($keydb, 'environment.project.uuid'), - 'environment_name' => data_get($keydb, 'environment.name'), - 'database_uuid' => data_get($keydb, 'uuid'), - ]); - } - - return $keydb; - }); - $this->dragonflies = $this->environment->dragonflies->load(['tags'])->sortBy('name'); - $this->dragonflies = $this->dragonflies->map(function ($dragonfly) { - if (data_get($dragonfly, 'environment.project.uuid')) { - $dragonfly->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($dragonfly, 'environment.project.uuid'), - 'environment_name' => data_get($dragonfly, 'environment.name'), - 'database_uuid' => data_get($dragonfly, 'uuid'), - ]); - } - - return $dragonfly; - }); - $this->clickhouses = $this->environment->clickhouses->load(['tags'])->sortBy('name'); - $this->clickhouses = $this->clickhouses->map(function ($clickhouse) { - if (data_get($clickhouse, 'environment.project.uuid')) { - $clickhouse->hrefLink = route('project.database.configuration', [ - 'project_uuid' => data_get($clickhouse, 'environment.project.uuid'), - 'environment_name' => data_get($clickhouse, 'environment.name'), - 'database_uuid' => data_get($clickhouse, 'uuid'), - ]); - } + return $db; + }); + } - return $clickhouse; - }); - $this->services = $this->environment->services->load(['tags'])->sortBy('name'); + // Load services with their tags and server + $this->services = $this->environment->services()->with([ + 'tags', + 'destination.server.settings', + ])->get()->sortBy('name'); $this->services = $this->services->map(function ($service) { - if (data_get($service, 'environment.project.uuid')) { - $service->hrefLink = route('project.service.configuration', [ - 'project_uuid' => data_get($service, 'environment.project.uuid'), - 'environment_name' => data_get($service, 'environment.name'), - 'service_uuid' => data_get($service, 'uuid'), - ]); - $service->status = $service->status(); - } + $service->hrefLink = route('project.service.configuration', [ + 'project_uuid' => $this->project->uuid, + 'service_uuid' => $service->uuid, + 'environment_name' => $this->environment->name, + ]); return $service; }); diff --git a/app/Livewire/Project/Shared/ScheduledTask/Executions.php b/app/Livewire/Project/Shared/ScheduledTask/Executions.php index 0710e37ffc..74eac7132e 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Executions.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Executions.php @@ -24,6 +24,14 @@ class Executions extends Component #[Locked] public ?string $serverTimezone = null; + public $currentPage = 1; + + public $logsPerPage = 100; + + public $selectedExecution = null; + + public $isPollingActive = false; + public function getListeners() { $teamId = Auth::user()->currentTeam()->id; @@ -54,16 +62,84 @@ public function mount($taskId) public function refreshExecutions(): void { $this->executions = $this->task->executions()->take(20)->get(); + if ($this->selectedKey) { + $this->selectedExecution = $this->task->executions()->find($this->selectedKey); + if ($this->selectedExecution && $this->selectedExecution->status !== 'running') { + $this->isPollingActive = false; + } + } } public function selectTask($key): void { if ($key == $this->selectedKey) { $this->selectedKey = null; + $this->selectedExecution = null; + $this->currentPage = 1; + $this->isPollingActive = false; return; } $this->selectedKey = $key; + $this->selectedExecution = $this->task->executions()->find($key); + $this->currentPage = 1; + + // Start polling if task is running + if ($this->selectedExecution && $this->selectedExecution->status === 'running') { + $this->isPollingActive = true; + } + } + + public function polling() + { + if ($this->selectedExecution && $this->isPollingActive) { + $this->selectedExecution->refresh(); + if ($this->selectedExecution->status !== 'running') { + $this->isPollingActive = false; + } + } + } + + public function loadMoreLogs() + { + $this->currentPage++; + } + + public function getLogLinesProperty() + { + if (! $this->selectedExecution) { + return collect(); + } + + if (! $this->selectedExecution->message) { + return collect(['Waiting for task output...']); + } + + $lines = collect(explode("\n", $this->selectedExecution->message)); + + return $lines->take($this->currentPage * $this->logsPerPage); + } + + public function downloadLogs(int $executionId) + { + $execution = $this->executions->firstWhere('id', $executionId); + if (! $execution) { + return; + } + + return response()->streamDownload(function () use ($execution) { + echo $execution->message; + }, 'task-execution-'.$execution->id.'.log'); + } + + public function hasMoreLogs() + { + if (! $this->selectedExecution || ! $this->selectedExecution->message) { + return false; + } + $lines = collect(explode("\n", $this->selectedExecution->message)); + + return $lines->count() > ($this->currentPage * $this->logsPerPage); } public function formatDateInServerTimezone($date) diff --git a/app/Livewire/Security/ApiTokens.php b/app/Livewire/Security/ApiTokens.php index fe68a8ba55..72684bdc63 100644 --- a/app/Livewire/Security/ApiTokens.php +++ b/app/Livewire/Security/ApiTokens.php @@ -11,13 +11,7 @@ class ApiTokens extends Component public $tokens = []; - public bool $viewSensitiveData = false; - - public bool $readOnly = true; - - public bool $rootAccess = false; - - public array $permissions = ['read-only']; + public array $permissions = ['read']; public $isApiEnabled; @@ -29,51 +23,28 @@ public function render() public function mount() { $this->isApiEnabled = InstanceSettings::get()->is_api_enabled; - $this->tokens = auth()->user()->tokens->sortByDesc('created_at'); - } - - public function updatedViewSensitiveData() - { - if ($this->viewSensitiveData) { - $this->permissions[] = 'view:sensitive'; - $this->permissions = array_diff($this->permissions, ['*']); - $this->rootAccess = false; - } else { - $this->permissions = array_diff($this->permissions, ['view:sensitive']); - } - $this->makeSureOneIsSelected(); + $this->getTokens(); } - public function updatedReadOnly() + private function getTokens() { - if ($this->readOnly) { - $this->permissions[] = 'read-only'; - $this->permissions = array_diff($this->permissions, ['*']); - $this->rootAccess = false; - } else { - $this->permissions = array_diff($this->permissions, ['read-only']); - } - $this->makeSureOneIsSelected(); + $this->tokens = auth()->user()->tokens->sortByDesc('created_at'); } - public function updatedRootAccess() + public function updatedPermissions($permissionToUpdate) { - if ($this->rootAccess) { - $this->permissions = ['*']; - $this->readOnly = false; - $this->viewSensitiveData = false; + if ($permissionToUpdate == 'root') { + $this->permissions = ['root']; + } elseif ($permissionToUpdate == 'read:sensitive' && ! in_array('read', $this->permissions)) { + $this->permissions[] = 'read'; + } elseif ($permissionToUpdate == 'deploy') { + $this->permissions = ['deploy']; } else { - $this->readOnly = true; - $this->permissions = ['read-only']; - } - } - - public function makeSureOneIsSelected() - { - if (count($this->permissions) == 0) { - $this->permissions = ['read-only']; - $this->readOnly = true; + if (count($this->permissions) == 0) { + $this->permissions = ['read']; + } } + sort($this->permissions); } public function addNewToken() @@ -82,8 +53,8 @@ public function addNewToken() $this->validate([ 'description' => 'required|min:3|max:255', ]); - $token = auth()->user()->createToken($this->description, $this->permissions); - $this->tokens = auth()->user()->tokens; + $token = auth()->user()->createToken($this->description, array_values($this->permissions)); + $this->getTokens(); session()->flash('token', $token->plainTextToken); } catch (\Exception $e) { return handleError($e, $this); @@ -92,8 +63,12 @@ public function addNewToken() public function revoke(int $id) { - $token = auth()->user()->tokens()->where('id', $id)->first(); - $token->delete(); - $this->tokens = auth()->user()->tokens; + try { + $token = auth()->user()->tokens()->where('id', $id)->firstOrFail(); + $token->delete(); + $this->getTokens(); + } catch (\Exception $e) { + return handleError($e, $this); + } } } diff --git a/app/Livewire/Server/Proxy.php b/app/Livewire/Server/Proxy.php index 0b069ddb90..4e325c1ff6 100644 --- a/app/Livewire/Server/Proxy.php +++ b/app/Livewire/Server/Proxy.php @@ -15,6 +15,8 @@ class Proxy extends Component public $proxy_settings = null; + public bool $redirect_enabled = true; + public ?string $redirect_url = null; protected $listeners = ['proxyStatusUpdated', 'saveConfiguration' => 'submit']; @@ -26,6 +28,7 @@ class Proxy extends Component public function mount() { $this->selectedProxy = $this->server->proxyType(); + $this->redirect_enabled = data_get($this->server, 'proxy.redirect_enabled', true); $this->redirect_url = data_get($this->server, 'proxy.redirect_url'); } @@ -38,7 +41,7 @@ public function changeProxy() { $this->server->proxy = null; $this->server->save(); - $this->dispatch('proxyChanged'); + $this->dispatch('reloadWindow'); } public function selectProxy($proxy_type) @@ -46,7 +49,7 @@ public function selectProxy($proxy_type) try { $this->server->changeProxy($proxy_type, async: false); $this->selectedProxy = $this->server->proxy->type; - $this->dispatch('proxyStatusUpdated'); + $this->dispatch('reloadWindow'); } catch (\Throwable $e) { return handleError($e, $this); } @@ -63,13 +66,25 @@ public function instantSave() } } + public function instantSaveRedirect() + { + try { + $this->server->proxy->redirect_enabled = $this->redirect_enabled; + $this->server->save(); + $this->server->setupDefaultRedirect(); + $this->dispatch('success', 'Proxy configuration saved.'); + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function submit() { try { SaveConfiguration::run($this->server, $this->proxy_settings); $this->server->proxy->redirect_url = $this->redirect_url; $this->server->save(); - $this->server->setupDefault404Redirect(); + $this->server->setupDefaultRedirect(); $this->dispatch('success', 'Proxy configuration saved.'); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Livewire/Server/Proxy/Deploy.php b/app/Livewire/Server/Proxy/Deploy.php index 8fcff85d6a..4f9d410923 100644 --- a/app/Livewire/Server/Proxy/Deploy.php +++ b/app/Livewire/Server/Proxy/Deploy.php @@ -65,7 +65,7 @@ public function proxyStatusUpdated() public function restart() { try { - $this->stop(forceStop: false); + $this->stop(); $this->dispatch('checkProxy'); } catch (\Throwable $e) { return handleError($e, $this); @@ -105,6 +105,7 @@ public function stop(bool $forceStop = true) $startTime = Carbon::now()->getTimestamp(); while ($process->running()) { + ray('running'); if (Carbon::now()->getTimestamp() - $startTime >= $timeout) { $this->forceStopContainer($containerName); break; diff --git a/app/Livewire/Settings/Index.php b/app/Livewire/Settings/Index.php index c1be35ced1..7adb0f8a79 100644 --- a/app/Livewire/Settings/Index.php +++ b/app/Livewire/Settings/Index.php @@ -23,9 +23,6 @@ class Index extends Component #[Validate('nullable|string|max:255')] public ?string $fqdn = null; - #[Validate('nullable|string|max:255')] - public ?string $resale_license = null; - #[Validate('required|integer|min:1025|max:65535')] public int $public_port_min; @@ -83,7 +80,6 @@ public function mount() } else { $this->settings = instanceSettings(); $this->fqdn = $this->settings->fqdn; - $this->resale_license = $this->settings->resale_license; $this->public_port_min = $this->settings->public_port_min; $this->public_port_max = $this->settings->public_port_max; $this->custom_dns_servers = $this->settings->custom_dns_servers; @@ -122,7 +118,6 @@ public function instantSave($isSave = true) } $this->settings->fqdn = $this->fqdn; - $this->settings->resale_license = $this->resale_license; $this->settings->public_port_min = $this->public_port_min; $this->settings->public_port_max = $this->public_port_max; $this->settings->custom_dns_servers = $this->custom_dns_servers; diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php index 61f720b3a7..1c5edb108a 100644 --- a/app/Livewire/SettingsEmail.php +++ b/app/Livewire/SettingsEmail.php @@ -3,6 +3,10 @@ namespace App\Livewire; use App\Models\InstanceSettings; +use App\Models\Team; +use App\Notifications\Test; +use Illuminate\Support\Facades\RateLimiter; +use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; use Livewire\Component; @@ -10,39 +14,48 @@ class SettingsEmail extends Component { public InstanceSettings $settings; + #[Locked] + public Team $team; + #[Validate(['boolean'])] public bool $smtpEnabled = false; + #[Validate(['nullable', 'email'])] + public ?string $smtpFromAddress = null; + + #[Validate(['nullable', 'string'])] + public ?string $smtpFromName = null; + + #[Validate(['nullable', 'string'])] + public ?string $smtpRecipients = null; + #[Validate(['nullable', 'string'])] public ?string $smtpHost = null; #[Validate(['nullable', 'numeric', 'min:1', 'max:65535'])] public ?int $smtpPort = null; - #[Validate(['nullable', 'string'])] - public ?string $smtpEncryption = null; + #[Validate(['nullable', 'string', 'in:tls,ssl,none'])] + public ?string $smtpEncryption = 'tls'; #[Validate(['nullable', 'string'])] public ?string $smtpUsername = null; - #[Validate(['nullable'])] + #[Validate(['nullable', 'string'])] public ?string $smtpPassword = null; #[Validate(['nullable', 'numeric'])] public ?int $smtpTimeout = null; - #[Validate(['nullable', 'email'])] - public ?string $smtpFromAddress = null; - - #[Validate(['nullable', 'string'])] - public ?string $smtpFromName = null; - #[Validate(['boolean'])] public bool $resendEnabled = false; #[Validate(['nullable', 'string'])] public ?string $resendApiKey = null; + #[Validate(['nullable', 'email'])] + public ?string $testEmailAddress = null; + public function mount() { if (isInstanceAdmin() === false) { @@ -50,6 +63,8 @@ public function mount() } $this->settings = instanceSettings(); $this->syncData(); + $this->team = auth()->user()->currentTeam(); + $this->testEmailAddress = auth()->user()->email; } public function syncData(bool $toModel = false) @@ -90,7 +105,7 @@ public function submit() try { $this->resetErrorBag(); $this->syncData(true); - $this->dispatch('success', 'Settings saved.'); + $this->dispatch('success', 'Transactional email settings updated.'); } catch (\Throwable $e) { return handleError($e, $this); } @@ -99,19 +114,129 @@ public function submit() public function instantSave(string $type) { try { + $this->resetErrorBag(); + + if ($type === 'SMTP') { + $this->submitSmtp(); + } elseif ($type === 'Resend') { + $this->submitResend(); + } + + } catch (\Throwable $e) { if ($type === 'SMTP') { - $this->resendEnabled = false; - } else { $this->smtpEnabled = false; + } elseif ($type === 'Resend') { + $this->resendEnabled = false; } - $this->syncData(true); - if ($this->smtpEnabled || $this->resendEnabled) { - $this->dispatch('success', "{$type} enabled."); - } else { - $this->dispatch('success', "{$type} disabled."); + + return handleError($e, $this); + } + } + + public function submitSmtp() + { + try { + $this->validate([ + 'smtpEnabled' => 'boolean', + 'smtpFromAddress' => 'required|email', + 'smtpFromName' => 'required|string', + 'smtpHost' => 'required|string', + 'smtpPort' => 'required|numeric', + 'smtpEncryption' => 'required|string|in:tls,ssl,none', + 'smtpUsername' => 'nullable|string', + 'smtpPassword' => 'nullable|string', + 'smtpTimeout' => 'nullable|numeric', + ], [ + 'smtpFromAddress.required' => 'From Address is required.', + 'smtpFromAddress.email' => 'Please enter a valid email address.', + 'smtpFromName.required' => 'From Name is required.', + 'smtpHost.required' => 'SMTP Host is required.', + 'smtpPort.required' => 'SMTP Port is required.', + 'smtpPort.numeric' => 'SMTP Port must be a number.', + 'smtpEncryption.required' => 'Encryption type is required.', + ]); + + $this->resendEnabled = false; + $this->settings->resend_enabled = false; + + $this->settings->smtp_enabled = $this->smtpEnabled; + $this->settings->smtp_host = $this->smtpHost; + $this->settings->smtp_port = $this->smtpPort; + $this->settings->smtp_encryption = $this->smtpEncryption; + $this->settings->smtp_username = $this->smtpUsername; + $this->settings->smtp_password = $this->smtpPassword; + $this->settings->smtp_timeout = $this->smtpTimeout; + $this->settings->smtp_from_address = $this->smtpFromAddress; + $this->settings->smtp_from_name = $this->smtpFromName; + + $this->settings->save(); + + $this->dispatch('success', 'SMTP settings updated.'); + } catch (\Throwable $e) { + $this->smtpEnabled = false; + + return handleError($e); + } + } + + public function submitResend() + { + try { + $this->validate([ + 'resendEnabled' => 'boolean', + 'resendApiKey' => 'required|string', + 'smtpFromAddress' => 'required|email', + 'smtpFromName' => 'required|string', + ], [ + 'resendApiKey.required' => 'Resend API Key is required.', + 'smtpFromAddress.required' => 'From Address is required.', + 'smtpFromAddress.email' => 'Please enter a valid email address.', + 'smtpFromName.required' => 'From Name is required.', + ]); + + $this->smtpEnabled = false; + $this->settings->smtp_enabled = false; + + $this->settings->resend_enabled = $this->resendEnabled; + $this->settings->resend_api_key = $this->resendApiKey; + $this->settings->smtp_from_address = $this->smtpFromAddress; + $this->settings->smtp_from_name = $this->smtpFromName; + + $this->settings->save(); + + $this->dispatch('success', 'Resend settings updated.'); + } catch (\Throwable $e) { + $this->resendEnabled = false; + + return handleError($e); + } + } + + 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->testEmailAddress, 'email')); + $this->dispatch('success', 'Test Email sent.'); + }, + $decaySeconds = 10, + ); + + if (! $executed) { + throw new \Exception('Too many messages sent!'); } } catch (\Throwable $e) { - return handleError($e, $this); + return handleError($e); } } } diff --git a/app/Livewire/SettingsOauth.php b/app/Livewire/SettingsOauth.php index 17b3b89a3b..d5f0be14a7 100644 --- a/app/Livewire/SettingsOauth.php +++ b/app/Livewire/SettingsOauth.php @@ -17,6 +17,7 @@ protected function rules() $carry["oauth_settings_map.$setting->provider.client_secret"] = 'nullable'; $carry["oauth_settings_map.$setting->provider.redirect_uri"] = 'nullable'; $carry["oauth_settings_map.$setting->provider.tenant"] = 'nullable'; + $carry["oauth_settings_map.$setting->provider.base_url"] = 'nullable'; return $carry; }, []); diff --git a/app/Livewire/Source/Github/Change.php b/app/Livewire/Source/Github/Change.php index 07cef54f9f..4679274848 100644 --- a/app/Livewire/Source/Github/Change.php +++ b/app/Livewire/Source/Github/Change.php @@ -4,6 +4,11 @@ use App\Jobs\GithubAppPermissionJob; use App\Models\GithubApp; +use App\Models\PrivateKey; +use Illuminate\Support\Facades\Http; +use Lcobucci\JWT\Configuration; +use Lcobucci\JWT\Signer\Key\InMemory; +use Lcobucci\JWT\Signer\Rsa\Sha256; use Livewire\Component; class Change extends Component @@ -51,12 +56,20 @@ class Change extends Component 'github_app.administration' => 'nullable|string', ]; + public function boot() + { + if ($this->github_app) { + $this->github_app->makeVisible(['client_secret', 'webhook_secret']); + } + } + public function checkPermissions() { GithubAppPermissionJob::dispatchSync($this->github_app); $this->github_app->refresh()->makeVisible('client_secret')->makeVisible('webhook_secret'); $this->dispatch('success', 'Github App permissions updated.'); } + // public function check() // { @@ -90,15 +103,16 @@ public function checkPermissions() // ray($runners_by_repository); // } + public function mount() { try { $github_app_uuid = request()->github_app_uuid; $this->github_app = GithubApp::ownedByCurrentTeam()->whereUuid($github_app_uuid)->firstOrFail(); + $this->github_app->makeVisible(['client_secret', 'webhook_secret']); $this->applications = $this->github_app->applications; $settings = instanceSettings(); - $this->github_app->makeVisible('client_secret')->makeVisible('webhook_secret'); $this->name = str($this->github_app->name)->kebab(); $this->fqdn = $settings->fqdn; @@ -142,6 +156,77 @@ public function mount() } } + public function getGithubAppNameUpdatePath() + { + if (str($this->github_app->organization)->isNotEmpty()) { + return "{$this->github_app->html_url}/organizations/{$this->github_app->organization}/settings/apps/{$this->github_app->name}"; + } + + return "{$this->github_app->html_url}/settings/apps/{$this->github_app->name}"; + } + + private function generateGithubJwt($private_key, $app_id): string + { + $configuration = Configuration::forAsymmetricSigner( + new Sha256, + InMemory::plainText($private_key), + InMemory::plainText($private_key) + ); + + $now = time(); + + return $configuration->builder() + ->issuedBy((string) $app_id) + ->permittedFor('https://api.github.com') + ->identifiedBy((string) $now) + ->issuedAt(new \DateTimeImmutable("@{$now}")) + ->expiresAt(new \DateTimeImmutable('@'.($now + 600))) + ->getToken($configuration->signer(), $configuration->signingKey()) + ->toString(); + } + + public function updateGithubAppName() + { + try { + $privateKey = PrivateKey::ownedByCurrentTeam()->find($this->github_app->private_key_id); + + if (! $privateKey) { + $this->dispatch('error', 'No private key found for this GitHub App.'); + + return; + } + + $jwt = $this->generateGithubJwt($privateKey->private_key, $this->github_app->app_id); + + $response = Http::withHeaders([ + 'Accept' => 'application/vnd.github+json', + 'X-GitHub-Api-Version' => '2022-11-28', + 'Authorization' => "Bearer {$jwt}", + ])->get("{$this->github_app->api_url}/app"); + + if ($response->successful()) { + $app_data = $response->json(); + $app_slug = $app_data['slug'] ?? null; + + if ($app_slug) { + $this->github_app->name = $app_slug; + $this->name = str($app_slug)->kebab(); + $privateKey->name = "github-app-{$app_slug}"; + $privateKey->save(); + $this->github_app->save(); + $this->dispatch('success', 'GitHub App name and SSH key name synchronized successfully.'); + } else { + $this->dispatch('info', 'Could not find App Name (slug) in GitHub response.'); + } + } else { + $error_message = $response->json()['message'] ?? 'Unknown error'; + $this->dispatch('error', "Failed to fetch GitHub App information: {$error_message}"); + } + } catch (\Throwable $e) { + return handleError($e, $this); + } + } + public function submit() { try { diff --git a/app/Livewire/Waitlist/Index.php b/app/Livewire/Waitlist/Index.php deleted file mode 100644 index 0524b495c1..0000000000 --- a/app/Livewire/Waitlist/Index.php +++ /dev/null @@ -1,70 +0,0 @@ - 'required|email', - ]; - - public function render() - { - return view('livewire.waitlist.index')->layout('layouts.simple'); - } - - public function mount() - { - if (config('constants.waitlist.enabled') == false) { - return redirect()->route('register'); - } - $this->waitingInLine = Waitlist::whereVerified(true)->count(); - $this->users = User::count(); - if (isDev()) { - $this->email = 'waitlist@example.com'; - } - } - - public function submit() - { - $this->validate(); - try { - $already_registered = User::whereEmail($this->email)->first(); - if ($already_registered) { - throw new \Exception('You are already on the waitlist or registered.
Please check your email to verify your email address or contact support.'); - } - $found = Waitlist::where('email', $this->email)->first(); - if ($found) { - if (! $found->verified) { - $this->dispatch('error', 'You are already on the waitlist.
Please check your email to verify your email address.'); - - return; - } - $this->dispatch('error', 'You are already on the waitlist.
You will be notified when your turn comes.
Thank you.'); - - return; - } - $waitlist = Waitlist::create([ - 'email' => Str::lower($this->email), - 'type' => 'registration', - ]); - - $this->dispatch('success', 'Check your email to verify your email address.'); - dispatch(new SendConfirmationForWaitlistJob($this->email, $waitlist->uuid)); - } catch (\Throwable $e) { - return handleError($e, $this); - } - } -} diff --git a/app/Models/Application.php b/app/Models/Application.php index a68c1d54a6..bfb2a10418 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -115,6 +115,12 @@ class Application extends BaseModel protected static function booted() { + static::addGlobalScope('withRelations', function ($builder) { + $builder->withCount([ + 'additional_servers', + 'additional_networks', + ]); + }); static::saving(function ($application) { $payload = []; if ($application->isDirty('fqdn')) { @@ -327,7 +333,7 @@ public function link() return null; } - public function failedTaskLink($task_uuid) + public function taskLink($task_uuid) { if (data_get($this, 'environment.project.uuid')) { $route = route('project.application.scheduled-tasks', [ @@ -551,20 +557,21 @@ protected function serverStatus(): Attribute { return Attribute::make( get: function () { - if ($this->additional_servers->count() === 0) { - return $this->destination->server->isFunctional(); - } else { - $additional_servers_status = $this->additional_servers->pluck('pivot.status'); - $main_server_status = $this->destination->server->isFunctional(); - foreach ($additional_servers_status as $status) { - $server_status = str($status)->before(':')->value(); - if ($server_status !== 'running') { - return false; - } - } + if (! $this->relationLoaded('additional_servers') || $this->additional_servers->count() === 0) { + return $this->destination?->server?->isFunctional() ?? false; + } + + $additional_servers_status = $this->additional_servers->pluck('pivot.status'); + $main_server_status = $this->destination?->server?->isFunctional() ?? false; - return $main_server_status; + foreach ($additional_servers_status as $status) { + $server_status = str($status)->before(':')->value(); + if ($server_status !== 'running') { + return false; + } } + + return $main_server_status; } ); } @@ -1321,17 +1328,45 @@ public function loadComposeFile($isInit = false) if (! $gitRemoteStatus['is_accessible']) { throw new \RuntimeException("Failed to read Git source:\n\n{$gitRemoteStatus['error']}"); } + $getGitVersion = instant_remote_process(['git --version'], $this->destination->server, false); + $gitVersion = str($getGitVersion)->explode(' ')->last(); + + if (version_compare($gitVersion, '2.35.1', '<')) { + $fileList = $fileList->map(function ($file) { + $parts = explode('/', trim($file, '.')); + $paths = collect(); + $currentPath = ''; + foreach ($parts as $part) { + $currentPath .= ($currentPath ? '/' : '').$part; + if (str($currentPath)->isNotEmpty()) { + $paths->push($currentPath); + } + } - $commands = collect([ - "rm -rf /tmp/{$uuid}", - "mkdir -p /tmp/{$uuid}", - "cd /tmp/{$uuid}", - $cloneCommand, - 'git sparse-checkout init --cone', - "git sparse-checkout set {$fileList->implode(' ')}", - 'git read-tree -mu HEAD', - "cat .$workdir$composeFile", - ]); + return $paths; + })->flatten()->unique()->values(); + $commands = collect([ + "rm -rf /tmp/{$uuid}", + "mkdir -p /tmp/{$uuid}", + "cd /tmp/{$uuid}", + $cloneCommand, + 'git sparse-checkout init', + "git sparse-checkout set {$fileList->implode(' ')}", + 'git read-tree -mu HEAD', + "cat .$workdir$composeFile", + ]); + } else { + $commands = collect([ + "rm -rf /tmp/{$uuid}", + "mkdir -p /tmp/{$uuid}", + "cd /tmp/{$uuid}", + $cloneCommand, + 'git sparse-checkout init --cone', + "git sparse-checkout set {$fileList->implode(' ')}", + 'git read-tree -mu HEAD', + "cat .$workdir$composeFile", + ]); + } try { $composeFileContent = instant_remote_process($commands, $this->destination->server); } catch (\Exception $e) { diff --git a/app/Models/BaseModel.php b/app/Models/BaseModel.php index 79801987b3..727abed5fe 100644 --- a/app/Models/BaseModel.php +++ b/app/Models/BaseModel.php @@ -20,7 +20,7 @@ protected static function boot() }); } - public function name(): Attribute + public function sanitizedName(): Attribute { return new Attribute( get: fn () => sanitize_string($this->getRawOriginal('name')), diff --git a/app/Models/DiscordNotificationSettings.php b/app/Models/DiscordNotificationSettings.php new file mode 100644 index 0000000000..619393ddc0 --- /dev/null +++ b/app/Models/DiscordNotificationSettings.php @@ -0,0 +1,59 @@ + 'boolean', + 'discord_webhook_url' => 'encrypted', + + 'deployment_success_discord_notifications' => 'boolean', + 'deployment_failure_discord_notifications' => 'boolean', + 'status_change_discord_notifications' => 'boolean', + 'backup_success_discord_notifications' => 'boolean', + 'backup_failure_discord_notifications' => 'boolean', + 'scheduled_task_success_discord_notifications' => 'boolean', + 'scheduled_task_failure_discord_notifications' => 'boolean', + 'docker_cleanup_discord_notifications' => 'boolean', + 'server_disk_usage_discord_notifications' => 'boolean', + 'server_reachable_discord_notifications' => 'boolean', + 'server_unreachable_discord_notifications' => 'boolean', + ]; + + public function team() + { + return $this->belongsTo(Team::class); + } + + public function isEnabled() + { + return $this->discord_enabled; + } +} diff --git a/app/Models/EmailNotificationSettings.php b/app/Models/EmailNotificationSettings.php new file mode 100644 index 0000000000..ae118986fc --- /dev/null +++ b/app/Models/EmailNotificationSettings.php @@ -0,0 +1,79 @@ + 'boolean', + 'smtp_from_address' => 'encrypted', + 'smtp_from_name' => 'encrypted', + 'smtp_recipients' => 'encrypted', + 'smtp_host' => 'encrypted', + 'smtp_port' => 'integer', + 'smtp_username' => 'encrypted', + 'smtp_password' => 'encrypted', + 'smtp_timeout' => 'integer', + + 'resend_enabled' => 'boolean', + 'resend_api_key' => 'encrypted', + + 'use_instance_email_settings' => 'boolean', + + 'deployment_success_email_notifications' => 'boolean', + 'deployment_failure_email_notifications' => 'boolean', + 'status_change_email_notifications' => 'boolean', + 'backup_success_email_notifications' => 'boolean', + 'backup_failure_email_notifications' => 'boolean', + 'scheduled_task_success_email_notifications' => 'boolean', + 'scheduled_task_failure_email_notifications' => 'boolean', + 'server_disk_usage_email_notifications' => 'boolean', + ]; + + public function team() + { + return $this->belongsTo(Team::class); + } + + public function isEnabled() + { + if (isCloud()) { + return true; + } + + return $this->smtp_enabled || $this->resend_enabled || $this->use_instance_email_settings; + } +} diff --git a/app/Models/InstanceSettings.php b/app/Models/InstanceSettings.php index eeb8039252..5b89bb401c 100644 --- a/app/Models/InstanceSettings.php +++ b/app/Models/InstanceSettings.php @@ -16,8 +16,19 @@ class InstanceSettings extends Model implements SendsEmail protected $guarded = []; protected $casts = [ - 'resale_license' => 'encrypted', + 'smtp_enabled' => 'boolean', + 'smtp_from_address' => 'encrypted', + 'smtp_from_name' => 'encrypted', + 'smtp_recipients' => 'encrypted', + 'smtp_host' => 'encrypted', + 'smtp_port' => 'integer', + 'smtp_username' => 'encrypted', 'smtp_password' => 'encrypted', + 'smtp_timeout' => 'integer', + + 'resend_enabled' => 'boolean', + 'resend_api_key' => 'encrypted', + 'allowed_ip_ranges' => 'array', 'is_auto_update_enabled' => 'boolean', 'auto_update_frequency' => 'string', @@ -81,7 +92,7 @@ public static function get() return InstanceSettings::findOrFail(0); } - public function getRecepients($notification) + public function getRecipients($notification) { $recipients = data_get($notification, 'emails', null); if (is_null($recipients) || $recipients === '') { diff --git a/app/Models/PushoverNotificationSettings.php b/app/Models/PushoverNotificationSettings.php new file mode 100644 index 0000000000..e3191dcc34 --- /dev/null +++ b/app/Models/PushoverNotificationSettings.php @@ -0,0 +1,61 @@ + 'boolean', + 'pushover_user_key' => 'encrypted', + 'pushover_api_token' => 'encrypted', + + 'deployment_success_pushover_notifications' => 'boolean', + 'deployment_failure_pushover_notifications' => 'boolean', + 'status_change_pushover_notifications' => 'boolean', + 'backup_success_pushover_notifications' => 'boolean', + 'backup_failure_pushover_notifications' => 'boolean', + 'scheduled_task_success_pushover_notifications' => 'boolean', + 'scheduled_task_failure_pushover_notifications' => 'boolean', + 'docker_cleanup_pushover_notifications' => 'boolean', + 'server_disk_usage_pushover_notifications' => 'boolean', + 'server_reachable_pushover_notifications' => 'boolean', + 'server_unreachable_pushover_notifications' => 'boolean', + ]; + + public function team() + { + return $this->belongsTo(Team::class); + } + + public function isEnabled() + { + return $this->pushover_enabled; + } +} diff --git a/app/Models/S3Storage.php b/app/Models/S3Storage.php index a432a6e9c3..f1247e6f74 100644 --- a/app/Models/S3Storage.php +++ b/app/Models/S3Storage.php @@ -59,7 +59,7 @@ public function testConnection(bool $shouldSave = false) $this->is_usable = true; } catch (\Throwable $e) { $this->is_usable = false; - if ($this->unusable_email_sent === false && is_transactional_emails_active()) { + if ($this->unusable_email_sent === false && is_transactional_emails_enabled()) { $mail = new MailMessage; $mail->subject('Coolify: S3 Storage Connection Error'); $mail->view('emails.s3-connection-error', ['name' => $this->name, 'reason' => $e->getMessage(), 'url' => route('storage.show', ['storage_uuid' => $this->uuid])]); diff --git a/app/Models/Server.php b/app/Models/Server.php index e0a66c58b7..cc82117890 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -105,6 +105,14 @@ protected static function booted() ]); } } + if (! isset($server->proxy->redirect_enabled)) { + $server->proxy->redirect_enabled = true; + } + }); + static::retrieved(function ($server) { + if (! isset($server->proxy->redirect_enabled)) { + $server->proxy->redirect_enabled = true; + } }); static::forceDeleting(function ($server) { @@ -184,73 +192,80 @@ public function proxySet() return $this->proxyType() && $this->proxyType() !== 'NONE' && $this->isFunctional() && ! $this->isSwarmWorker() && ! $this->settings->is_build_server; } - public function setupDefault404Redirect() + public function setupDefaultRedirect() { + $banner = + "# This file is generated by Coolify, do not edit it manually.\n". + "# Disable the default redirect to customize (only if you know what are you doing).\n\n"; $dynamic_conf_path = $this->proxyPath().'/dynamic'; $proxy_type = $this->proxyType(); + $redirect_enabled = $this->proxy->redirect_enabled ?? true; $redirect_url = $this->proxy->redirect_url; + if (isDev()) { + if ($proxy_type === ProxyTypes::CADDY->value) { + $dynamic_conf_path = '/data/coolify/proxy/caddy/dynamic'; + } + } if ($proxy_type === ProxyTypes::TRAEFIK->value) { - $default_redirect_file = "$dynamic_conf_path/default_redirect_404.yaml"; + $default_redirect_file = "$dynamic_conf_path/default_redirect_503.yaml"; } elseif ($proxy_type === ProxyTypes::CADDY->value) { - $default_redirect_file = "$dynamic_conf_path/default_redirect_404.caddy"; + $default_redirect_file = "$dynamic_conf_path/default_redirect_503.caddy"; } - if (empty($redirect_url)) { - if ($proxy_type === ProxyTypes::CADDY->value) { - $conf = ':80, :443 { -respond 404 -}'; - $conf = - "# This file is automatically generated by Coolify.\n". - "# Do not edit it manually (only if you know what are you doing).\n\n". - $conf; - $base64 = base64_encode($conf); - instant_remote_process([ - "mkdir -p $dynamic_conf_path", - "echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null", - ], $this); - $this->reloadCaddy(); - return; - } - instant_remote_process([ - "mkdir -p $dynamic_conf_path", - "rm -f $default_redirect_file", - ], $this); + instant_remote_process([ + "mkdir -p $dynamic_conf_path", + "rm -f $dynamic_conf_path/default_redirect_404.yaml", + "rm -f $dynamic_conf_path/default_redirect_404.caddy", + ], $this); - return; - } - if ($proxy_type === ProxyTypes::TRAEFIK->value) { - $dynamic_conf = [ - 'http' => [ - 'routers' => [ - 'catchall' => [ - 'entryPoints' => [ - 0 => 'http', - 1 => 'https', - ], - 'service' => 'noop', - 'rule' => 'HostRegexp(`.+`)', - 'tls' => [ - 'certResolver' => 'letsencrypt', - ], - 'priority' => 1, - 'middlewares' => [ - 0 => 'redirect-regexp', + if ($redirect_enabled === false) { + instant_remote_process(["rm -f $default_redirect_file"], $this); + } else { + if ($proxy_type === ProxyTypes::CADDY->value) { + if (filled($redirect_url)) { + $conf = ":80, :443 { + redir $redirect_url +}"; + } else { + $conf = ':80, :443 { + respond 503 +}'; + } + } elseif ($proxy_type === ProxyTypes::TRAEFIK->value) { + $dynamic_conf = [ + 'http' => [ + 'routers' => [ + 'catchall' => [ + 'entryPoints' => [ + 0 => 'http', + 1 => 'https', + ], + 'service' => 'noop', + 'rule' => 'PathPrefix(`/`)', + 'tls' => [ + 'certResolver' => 'letsencrypt', + ], + 'priority' => -1000, ], ], - ], - 'services' => [ - 'noop' => [ - 'loadBalancer' => [ - 'servers' => [ - 0 => [ - 'url' => '', - ], + 'services' => [ + 'noop' => [ + 'loadBalancer' => [ + 'servers' => [], ], ], ], ], - 'middlewares' => [ + ]; + if (filled($redirect_url)) { + $dynamic_conf['http']['routers']['catchall']['middlewares'] = [ + 0 => 'redirect-regexp', + ]; + + $dynamic_conf['http']['services']['noop']['loadBalancer']['servers'][0] = [ + 'url' => '', + ]; + $dynamic_conf['http']['middlewares'] = [ 'redirect-regexp' => [ 'redirectRegex' => [ 'regex' => '(.*)', @@ -258,32 +273,17 @@ public function setupDefault404Redirect() 'permanent' => false, ], ], - ], - ], - ]; - $conf = Yaml::dump($dynamic_conf, 12, 2); - $conf = - "# This file is automatically generated by Coolify.\n". - "# Do not edit it manually (only if you know what are you doing).\n\n". - $conf; - - $base64 = base64_encode($conf); - } elseif ($proxy_type === ProxyTypes::CADDY->value) { - $conf = ":80, :443 { - redir $redirect_url -}"; - $conf = - "# This file is automatically generated by Coolify.\n". - "# Do not edit it manually (only if you know what are you doing).\n\n". - $conf; + ]; + } + $conf = Yaml::dump($dynamic_conf, 12, 2); + } + $conf = $banner.$conf; $base64 = base64_encode($conf); + instant_remote_process([ + "echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null", + ], $this); } - instant_remote_process([ - "mkdir -p $dynamic_conf_path", - "echo '$base64' | base64 -d | tee $default_redirect_file > /dev/null", - ], $this); - if ($proxy_type === 'CADDY') { $this->reloadCaddy(); } @@ -611,7 +611,9 @@ public function getMemoryMetrics(int $mins = 5) } $memory = json_decode($memory, true); $parsedCollection = collect($memory)->map(function ($metric) { - return [(int) $metric['time'], (float) $metric['usedPercent']]; + $usedPercent = $metric['usedPercent'] ?? 0.0; + + return [(int) $metric['time'], (float) $usedPercent]; }); return $parsedCollection->toArray(); @@ -1040,7 +1042,7 @@ public function sendReachableNotification() $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,7 +1050,7 @@ public function sendUnreachableNotification() $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 $justCheckingNewKey = false) diff --git a/app/Models/Service.php b/app/Models/Service.php index 6d3d2024b0..117677d532 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -1140,7 +1140,7 @@ public function link() return null; } - public function failedTaskLink($task_uuid) + public function taskLink($task_uuid) { if (data_get($this, 'environment.project.uuid')) { $route = route('project.service.scheduled-tasks', [ diff --git a/app/Models/SlackNotificationSettings.php b/app/Models/SlackNotificationSettings.php new file mode 100644 index 0000000000..48153f2ea8 --- /dev/null +++ b/app/Models/SlackNotificationSettings.php @@ -0,0 +1,59 @@ + 'boolean', + 'slack_webhook_url' => 'encrypted', + + 'deployment_success_slack_notifications' => 'boolean', + 'deployment_failure_slack_notifications' => 'boolean', + 'status_change_slack_notifications' => 'boolean', + 'backup_success_slack_notifications' => 'boolean', + 'backup_failure_slack_notifications' => 'boolean', + 'scheduled_task_success_slack_notifications' => 'boolean', + 'scheduled_task_failure_slack_notifications' => 'boolean', + 'docker_cleanup_slack_notifications' => 'boolean', + 'server_disk_usage_slack_notifications' => 'boolean', + 'server_reachable_slack_notifications' => 'boolean', + 'server_unreachable_slack_notifications' => 'boolean', + ]; + + public function team() + { + return $this->belongsTo(Team::class); + } + + public function isEnabled() + { + return $this->slack_enabled; + } +} diff --git a/app/Models/Team.php b/app/Models/Team.php index e21aa3a25c..e55cb0d19d 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -4,6 +4,9 @@ use App\Notifications\Channels\SendsDiscord; use App\Notifications\Channels\SendsEmail; +use App\Notifications\Channels\SendsPushover; +use App\Notifications\Channels\SendsSlack; +use App\Traits\HasNotificationSettings; use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; @@ -19,49 +22,8 @@ 'personal_team' => ['type' => 'boolean', 'description' => 'Whether the team is personal or not.'], 'created_at' => ['type' => 'string', 'description' => 'The date and time the team was created.'], 'updated_at' => ['type' => 'string', 'description' => 'The date and time the team was last updated.'], - 'smtp_enabled' => ['type' => 'boolean', 'description' => 'Whether SMTP is enabled or not.'], - 'smtp_from_address' => ['type' => 'string', 'description' => 'The email address to send emails from.'], - 'smtp_from_name' => ['type' => 'string', 'description' => 'The name to send emails from.'], - 'smtp_recipients' => ['type' => 'string', 'description' => 'The email addresses to send emails to.'], - 'smtp_host' => ['type' => 'string', 'description' => 'The SMTP host.'], - 'smtp_port' => ['type' => 'string', 'description' => 'The SMTP port.'], - 'smtp_encryption' => ['type' => 'string', 'description' => 'The SMTP encryption.'], - 'smtp_username' => ['type' => 'string', 'description' => 'The SMTP username.'], - 'smtp_password' => ['type' => 'string', 'description' => 'The SMTP password.'], - 'smtp_timeout' => ['type' => 'string', 'description' => 'The SMTP timeout.'], - 'smtp_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via SMTP.'], - 'smtp_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via SMTP.'], - 'smtp_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via SMTP.'], - 'smtp_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via SMTP.'], - 'smtp_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via SMTP.'], - 'smtp_notifications_server_disk_usage' => ['type' => 'boolean', 'description' => 'Whether to send server disk usage notifications via SMTP.'], - 'discord_enabled' => ['type' => 'boolean', 'description' => 'Whether Discord is enabled or not.'], - 'discord_webhook_url' => ['type' => 'string', 'description' => 'The Discord webhook URL.'], - 'discord_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via Discord.'], - 'discord_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via Discord.'], - 'discord_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via Discord.'], - 'discord_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via Discord.'], - 'discord_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Discord.'], - 'discord_notifications_server_disk_usage' => ['type' => 'boolean', 'description' => 'Whether to send server disk usage notifications via Discord.'], 'show_boarding' => ['type' => 'boolean', 'description' => 'Whether to show the boarding screen or not.'], - 'resend_enabled' => ['type' => 'boolean', 'description' => 'Whether to enable resending or not.'], - 'resend_api_key' => ['type' => 'string', 'description' => 'The resending API key.'], - 'use_instance_email_settings' => ['type' => 'boolean', 'description' => 'Whether to use instance email settings or not.'], - 'telegram_enabled' => ['type' => 'boolean', 'description' => 'Whether Telegram is enabled or not.'], - 'telegram_token' => ['type' => 'string', 'description' => 'The Telegram token.'], - 'telegram_chat_id' => ['type' => 'string', 'description' => 'The Telegram chat ID.'], - 'telegram_notifications_test' => ['type' => 'boolean', 'description' => 'Whether to send test notifications via Telegram.'], - 'telegram_notifications_deployments' => ['type' => 'boolean', 'description' => 'Whether to send deployment notifications via Telegram.'], - 'telegram_notifications_status_changes' => ['type' => 'boolean', 'description' => 'Whether to send status change notifications via Telegram.'], - 'telegram_notifications_database_backups' => ['type' => 'boolean', 'description' => 'Whether to send database backup notifications via Telegram.'], - 'telegram_notifications_test_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram test message thread ID.'], - 'telegram_notifications_deployments_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram deployment message thread ID.'], - 'telegram_notifications_status_changes_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram status change message thread ID.'], - 'telegram_notifications_database_backups_message_thread_id' => ['type' => 'string', 'description' => 'The Telegram database backup message thread ID.'], - 'custom_server_limit' => ['type' => 'string', 'description' => 'The custom server limit.'], - 'telegram_notifications_scheduled_tasks' => ['type' => 'boolean', 'description' => 'Whether to send scheduled task notifications via Telegram.'], - 'telegram_notifications_scheduled_tasks_thread_id' => ['type' => 'string', 'description' => 'The Telegram scheduled task message thread ID.'], 'members' => new OA\Property( property: 'members', type: 'array', @@ -70,20 +32,27 @@ ), ] )] -class Team extends Model implements SendsDiscord, SendsEmail + +class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, SendsSlack { - use Notifiable; + use HasNotificationSettings, Notifiable; protected $guarded = []; protected $casts = [ 'personal_team' => 'boolean', - 'smtp_password' => 'encrypted', - 'resend_api_key' => 'encrypted', ]; protected static function booted() { + static::created(function ($team) { + $team->emailNotificationSettings()->create(); + $team->discordNotificationSettings()->create(); + $team->slackNotificationSettings()->create(); + $team->telegramNotificationSettings()->create(); + $team->pushoverNotificationSettings()->create(); + }); + static::saving(function ($team) { if (auth()->user()?->isMember()) { throw new \Exception('You are not allowed to update this team.'); @@ -114,29 +83,6 @@ protected static function booted() }); } - public function routeNotificationForDiscord() - { - return data_get($this, 'discord_webhook_url', null); - } - - public function routeNotificationForTelegram() - { - return [ - 'token' => data_get($this, 'telegram_token', null), - 'chat_id' => data_get($this, 'telegram_chat_id', null), - ]; - } - - public function getRecepients($notification) - { - $recipients = data_get($notification, 'emails', null); - if (is_null($recipients)) { - return $this->members()->pluck('email')->toArray(); - } - - return explode(',', $recipients); - } - public static function serverLimitReached() { $serverLimit = Team::serverLimit(); @@ -190,10 +136,75 @@ public function limits(): Attribute return $serverLimit ?? 2; } - ); } + public function routeNotificationForDiscord() + { + return data_get($this, 'discord_webhook_url', null); + } + + public function routeNotificationForTelegram() + { + return [ + 'token' => data_get($this, 'telegram_token', null), + 'chat_id' => data_get($this, 'telegram_chat_id', null), + ]; + } + + public function routeNotificationForSlack() + { + return data_get($this, 'slack_webhook_url', null); + } + + public function routeNotificationForPushover() + { + return [ + 'user' => data_get($this, 'pushover_user_key', null), + 'token' => data_get($this, 'pushover_api_token', null), + ]; + } + + public function getRecipients($notification) + { + $recipients = data_get($notification, 'emails', null); + if (is_null($recipients)) { + return $this->members()->pluck('email')->toArray(); + } + + return explode(',', $recipients); + } + + public function isAnyNotificationEnabled() + { + if (isCloud()) { + return true; + } + + return $this->getNotificationSettings('email')?->isEnabled() || + $this->getNotificationSettings('discord')?->isEnabled() || + $this->getNotificationSettings('slack')?->isEnabled() || + $this->getNotificationSettings('telegram')?->isEnabled() || + $this->getNotificationSettings('pushover')?->isEnabled(); + } + + public function subscriptionEnded() + { + $this->subscription->update([ + 'stripe_subscription_id' => null, + 'stripe_plan_id' => null, + 'stripe_cancel_at_period_end' => false, + 'stripe_invoice_paid' => false, + 'stripe_trial_already_ended' => false, + ]); + foreach ($this->servers as $server) { + $server->settings()->update([ + 'is_usable' => false, + 'is_reachable' => false, + ]); + } + } + public function environment_variables() { return $this->hasMany(SharedEnvironmentVariable::class)->whereNull('project_id')->whereNull('environment_id'); @@ -257,32 +268,28 @@ public function s3s() return $this->hasMany(S3Storage::class)->where('is_usable', true); } - public function subscriptionEnded() + public function emailNotificationSettings() { - $this->subscription->update([ - 'stripe_subscription_id' => null, - 'stripe_plan_id' => null, - 'stripe_cancel_at_period_end' => false, - 'stripe_invoice_paid' => false, - 'stripe_trial_already_ended' => false, - ]); - foreach ($this->servers as $server) { - $server->settings()->update([ - 'is_usable' => false, - 'is_reachable' => false, - ]); - } + return $this->hasOne(EmailNotificationSettings::class); } - public function isAnyNotificationEnabled() + public function discordNotificationSettings() { - if (isCloud()) { - return true; - } - if ($this->smtp_enabled || $this->resend_enabled || $this->discord_enabled || $this->telegram_enabled || $this->use_instance_email_settings) { - return true; - } + return $this->hasOne(DiscordNotificationSettings::class); + } - return false; + public function telegramNotificationSettings() + { + return $this->hasOne(TelegramNotificationSettings::class); + } + + public function slackNotificationSettings() + { + return $this->hasOne(SlackNotificationSettings::class); + } + + public function pushoverNotificationSettings() + { + return $this->hasOne(PushoverNotificationSettings::class); } } diff --git a/app/Models/TelegramNotificationSettings.php b/app/Models/TelegramNotificationSettings.php new file mode 100644 index 0000000000..78bd841bd4 --- /dev/null +++ b/app/Models/TelegramNotificationSettings.php @@ -0,0 +1,85 @@ + 'boolean', + 'telegram_token' => 'encrypted', + 'telegram_chat_id' => 'encrypted', + + 'deployment_success_telegram_notifications' => 'boolean', + 'deployment_failure_telegram_notifications' => 'boolean', + 'status_change_telegram_notifications' => 'boolean', + 'backup_success_telegram_notifications' => 'boolean', + 'backup_failure_telegram_notifications' => 'boolean', + 'scheduled_task_success_telegram_notifications' => 'boolean', + 'scheduled_task_failure_telegram_notifications' => 'boolean', + 'docker_cleanup_telegram_notifications' => 'boolean', + 'server_disk_usage_telegram_notifications' => 'boolean', + 'server_reachable_telegram_notifications' => 'boolean', + 'server_unreachable_telegram_notifications' => 'boolean', + + 'telegram_notifications_deployment_success_thread_id' => 'encrypted', + 'telegram_notifications_deployment_failure_thread_id' => 'encrypted', + 'telegram_notifications_status_change_thread_id' => 'encrypted', + 'telegram_notifications_backup_success_thread_id' => 'encrypted', + 'telegram_notifications_backup_failure_thread_id' => 'encrypted', + 'telegram_notifications_scheduled_task_success_thread_id' => 'encrypted', + 'telegram_notifications_scheduled_task_failure_thread_id' => 'encrypted', + 'telegram_notifications_docker_cleanup_thread_id' => 'encrypted', + 'telegram_notifications_server_disk_usage_thread_id' => 'encrypted', + 'telegram_notifications_server_reachable_thread_id' => 'encrypted', + 'telegram_notifications_server_unreachable_thread_id' => 'encrypted', + ]; + + public function team() + { + return $this->belongsTo(Team::class); + } + + public function isEnabled() + { + return $this->telegram_enabled; + } +} diff --git a/app/Models/User.php b/app/Models/User.php index 25fb33d668..7c23631c36 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -114,7 +114,7 @@ public function teams() return $this->belongsToMany(Team::class)->withPivot('role'); } - public function getRecepients($notification) + public function getRecipients($notification) { return $this->email; } diff --git a/app/Models/Waitlist.php b/app/Models/Waitlist.php deleted file mode 100644 index 28e5f01fd1..0000000000 --- a/app/Models/Waitlist.php +++ /dev/null @@ -1,12 +0,0 @@ -getEnabledChannels('deployment_failure'); } public function toMail(): MailMessage @@ -128,4 +130,56 @@ public function toTelegram(): array ], ]; } + + public function toPushover(): PushoverMessage + { + if ($this->preview) { + $title = "Pull request #{$this->preview->pull_request_id} deployment failed"; + $message = "Pull request deployment failed for {$this->application_name}"; + } else { + $title = 'Deployment failed'; + $message = "Deployment failed for {$this->application_name}"; + } + + $buttons[] = [ + 'text' => 'Deployment logs', + 'url' => $this->deployment_url, + ]; + + return new PushoverMessage( + title: $title, + level: 'error', + message: $message, + buttons: [ + ...$buttons, + ], + ); + } + + public function toSlack(): SlackMessage + { + if ($this->preview) { + $title = "Pull request #{$this->preview->pull_request_id} deployment failed"; + $description = "Pull request deployment failed for {$this->application_name}"; + if ($this->preview->fqdn) { + $description .= "\nPreview URL: {$this->preview->fqdn}"; + } + } else { + $title = 'Deployment failed'; + $description = "Deployment failed for {$this->application_name}"; + if ($this->fqdn) { + $description .= "\nApplication URL: {$this->fqdn}"; + } + } + + $description .= "\n\n**Project:** ".data_get($this->application, 'environment.project.name'); + $description .= "\n**Environment:** {$this->environment_name}"; + $description .= "\n**Deployment Logs:** {$this->deployment_url}"; + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php index 3916012571..b1a3d5225d 100644 --- a/app/Notifications/Application/DeploymentSuccess.php +++ b/app/Notifications/Application/DeploymentSuccess.php @@ -6,6 +6,8 @@ use App\Models\ApplicationPreview; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; class DeploymentSuccess extends CustomEmailNotification @@ -44,13 +46,7 @@ public function __construct(Application $application, string $deployment_uuid, ? public function via(object $notifiable): array { - $channels = setNotificationChannels($notifiable, 'deployments'); - if (isCloud()) { - // TODO: Make batch notifications work with email - $channels = array_diff($channels, [\App\Notifications\Channels\EmailChannel::class]); - } - - return $channels; + return $notifiable->getEnabledChannels('deployment_success'); } public function toMail(): MailMessage @@ -143,4 +139,67 @@ public function toTelegram(): array ], ]; } + + public function toPushover(): PushoverMessage + { + if ($this->preview) { + $title = "Pull request #{$this->preview->pull_request_id} successfully deployed"; + $message = 'New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . ''; + if ($this->preview->fqdn) { + $buttons[] = [ + 'text' => 'Open Application', + 'url' => $this->preview->fqdn, + ]; + } + } else { + $title = 'New version successfully deployed'; + $message = 'New version successfully deployed of ' . $this->application_name . ''; + if ($this->fqdn) { + $buttons[] = [ + 'text' => 'Open Application', + 'url' => $this->fqdn, + ]; + } + } + $buttons[] = [ + 'text' => 'Deployment logs', + 'url' => $this->deployment_url, + ]; + + return new PushoverMessage( + title: $title, + level: 'success', + message: $message, + buttons: [ + ...$buttons, + ], + ); + } + + public function toSlack(): SlackMessage + { + if ($this->preview) { + $title = "Pull request #{$this->preview->pull_request_id} successfully deployed"; + $description = "New version successfully deployed for {$this->application_name}"; + if ($this->preview->fqdn) { + $description .= "\nPreview URL: {$this->preview->fqdn}"; + } + } else { + $title = 'New version successfully deployed'; + $description = "New version successfully deployed for {$this->application_name}"; + if ($this->fqdn) { + $description .= "\nApplication URL: {$this->fqdn}"; + } + } + + $description .= "\n\n**Project:** ".data_get($this->application, 'environment.project.name'); + $description .= "\n**Environment:** {$this->environment_name}"; + $description .= "\n**Deployment Logs:** {$this->deployment_url}"; + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::successColor() + ); + } } diff --git a/app/Notifications/Application/StatusChanged.php b/app/Notifications/Application/StatusChanged.php index c757495cb5..c9c7344c42 100644 --- a/app/Notifications/Application/StatusChanged.php +++ b/app/Notifications/Application/StatusChanged.php @@ -5,6 +5,8 @@ use App\Models\Application; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; class StatusChanged extends CustomEmailNotification @@ -34,7 +36,7 @@ public function __construct(public Application $resource) public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'status_changes'); + return $notifiable->getEnabledChannels('status_change'); } public function toMail(): MailMessage @@ -75,4 +77,37 @@ public function toTelegram(): array ], ]; } + + public function toPushover(): PushoverMessage + { + $message = $this->resource_name . ' has been stopped.'; + + return new PushoverMessage( + title: 'Application stopped', + level: 'error', + message: $message, + buttons: [ + [ + 'text' => 'Open Application in Coolify', + 'url' => $this->resource_url, + ], + ], + ); + } + + public function toSlack(): SlackMessage + { + $title = 'Application stopped'; + $description = "{$this->resource_name} has been stopped"; + + $description .= "\n\n**Project:** ".data_get($this->resource, 'environment.project.name'); + $description .= "\n**Environment:** {$this->environment_name}"; + $description .= "\n**Application URL:** {$this->resource_url}"; + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/Channels/DiscordChannel.php b/app/Notifications/Channels/DiscordChannel.php index df7040f8fb..362006d8ec 100644 --- a/app/Notifications/Channels/DiscordChannel.php +++ b/app/Notifications/Channels/DiscordChannel.php @@ -13,10 +13,13 @@ class DiscordChannel public function send(SendsDiscord $notifiable, Notification $notification): void { $message = $notification->toDiscord(); - $webhookUrl = $notifiable->routeNotificationForDiscord(); - if (! $webhookUrl) { + + $discordSettings = $notifiable->discordNotificationSettings; + + if (! $discordSettings || ! $discordSettings->isEnabled() || ! $discordSettings->discord_webhook_url) { return; } - SendMessageToDiscordJob::dispatch($message, $webhookUrl); + + SendMessageToDiscordJob::dispatch($message, $discordSettings->discord_webhook_url); } } diff --git a/app/Notifications/Channels/EmailChannel.php b/app/Notifications/Channels/EmailChannel.php index af9af978d0..0985f43933 100644 --- a/app/Notifications/Channels/EmailChannel.php +++ b/app/Notifications/Channels/EmailChannel.php @@ -13,7 +13,7 @@ public function send(SendsEmail $notifiable, Notification $notification): void { try { $this->bootConfigs($notifiable); - $recipients = $notifiable->getRecepients($notification); + $recipients = $notifiable->getRecipients($notification); if (count($recipients) === 0) { throw new Exception('No email recipients found'); } @@ -46,7 +46,9 @@ public function send(SendsEmail $notifiable, Notification $notification): void private function bootConfigs($notifiable): void { - if (data_get($notifiable, 'use_instance_email_settings')) { + $emailSettings = $notifiable->emailNotificationSettings; + + if ($emailSettings->use_instance_email_settings) { $type = set_transanctional_email_settings(); if (! $type) { throw new Exception('No email settings found.'); @@ -54,23 +56,27 @@ private function bootConfigs($notifiable): void return; } - config()->set('mail.from.address', data_get($notifiable, 'smtp_from_address', 'test@example.com')); - config()->set('mail.from.name', data_get($notifiable, 'smtp_from_name', 'Test')); - if (data_get($notifiable, 'resend_enabled')) { + + config()->set('mail.from.address', $emailSettings->smtp_from_address ?? 'test@example.com'); + config()->set('mail.from.name', $emailSettings->smtp_from_name ?? 'Test'); + + if ($emailSettings->resend_enabled) { config()->set('mail.default', 'resend'); - config()->set('resend.api_key', data_get($notifiable, 'resend_api_key')); + config()->set('resend.api_key', $emailSettings->resend_api_key); } - if (data_get($notifiable, 'smtp_enabled')) { + + if ($emailSettings->smtp_enabled) { config()->set('mail.default', 'smtp'); config()->set('mail.mailers.smtp', [ 'transport' => 'smtp', - 'host' => data_get($notifiable, 'smtp_host'), - 'port' => data_get($notifiable, 'smtp_port'), - 'encryption' => data_get($notifiable, 'smtp_encryption'), - 'username' => data_get($notifiable, 'smtp_username'), - 'password' => data_get($notifiable, 'smtp_password'), - 'timeout' => data_get($notifiable, 'smtp_timeout'), + 'host' => $emailSettings->smtp_host, + 'port' => $emailSettings->smtp_port, + 'encryption' => $emailSettings->smtp_encryption === 'none' ? null : $emailSettings->smtp_encryption, + 'username' => $emailSettings->smtp_username, + 'password' => $emailSettings->smtp_password, + 'timeout' => $emailSettings->smtp_timeout, 'local_domain' => null, + 'auto_tls' => $emailSettings->smtp_encryption === 'none' ? '0' : '', ]); } } diff --git a/app/Notifications/Channels/PushoverChannel.php b/app/Notifications/Channels/PushoverChannel.php new file mode 100644 index 0000000000..3d3728d01f --- /dev/null +++ b/app/Notifications/Channels/PushoverChannel.php @@ -0,0 +1,21 @@ +toPushover(); + $pushoverSettings = $notifiable->pushoverNotificationSettings; + + if (! $pushoverSettings || ! $pushoverSettings->isEnabled() || ! $pushoverSettings->pushover_user_key || ! $pushoverSettings->pushover_api_token) { + return; + } + + SendMessageToPushoverJob::dispatch($message, $pushoverSettings->pushover_api_token, $pushoverSettings->pushover_user_key); + } +} diff --git a/app/Notifications/Channels/SendsEmail.php b/app/Notifications/Channels/SendsEmail.php index fc7528834a..3adc6d0a21 100644 --- a/app/Notifications/Channels/SendsEmail.php +++ b/app/Notifications/Channels/SendsEmail.php @@ -4,5 +4,5 @@ interface SendsEmail { - public function getRecepients($notification); + public function getRecipients($notification); } diff --git a/app/Notifications/Channels/SendsPushover.php b/app/Notifications/Channels/SendsPushover.php new file mode 100644 index 0000000000..7922eefb44 --- /dev/null +++ b/app/Notifications/Channels/SendsPushover.php @@ -0,0 +1,8 @@ +toSlack(); + $slackSettings = $notifiable->slackNotificationSettings; + + if (! $slackSettings || ! $slackSettings->isEnabled() || ! $slackSettings->slack_webhook_url) { + return; + } + + SendMessageToSlackJob::dispatch($message, $slackSettings->slack_webhook_url); + } +} diff --git a/app/Notifications/Channels/TelegramChannel.php b/app/Notifications/Channels/TelegramChannel.php index 958c46c21f..ea4ab71715 100644 --- a/app/Notifications/Channels/TelegramChannel.php +++ b/app/Notifications/Channels/TelegramChannel.php @@ -9,38 +9,39 @@ class TelegramChannel public function send($notifiable, $notification): void { $data = $notification->toTelegram($notifiable); - $telegramData = $notifiable->routeNotificationForTelegram(); + $settings = $notifiable->telegramNotificationSettings; + $message = data_get($data, 'message'); $buttons = data_get($data, 'buttons', []); - $telegramToken = data_get($telegramData, 'token'); - $chatId = data_get($telegramData, 'chat_id'); - $topicId = null; - $topicsInstance = get_class($notification); - - switch ($topicsInstance) { - case \App\Notifications\Test::class: - $topicId = data_get($notifiable, 'telegram_notifications_test_message_thread_id'); - break; - case \App\Notifications\Application\StatusChanged::class: - case \App\Notifications\Container\ContainerRestarted::class: - case \App\Notifications\Container\ContainerStopped::class: - $topicId = data_get($notifiable, 'telegram_notifications_status_changes_message_thread_id'); - break; - case \App\Notifications\Application\DeploymentSuccess::class: - case \App\Notifications\Application\DeploymentFailed::class: - $topicId = data_get($notifiable, 'telegram_notifications_deployments_message_thread_id'); - break; - case \App\Notifications\Database\BackupSuccess::class: - case \App\Notifications\Database\BackupFailed::class: - $topicId = data_get($notifiable, 'telegram_notifications_database_backups_message_thread_id'); - break; - case \App\Notifications\ScheduledTask\TaskFailed::class: - $topicId = data_get($notifiable, 'telegram_notifications_scheduled_tasks_thread_id'); - break; - } + $telegramToken = $settings->telegram_token; + $chatId = $settings->telegram_chat_id; + + $threadId = match (get_class($notification)) { + \App\Notifications\Application\DeploymentSuccess::class => $settings->telegram_notifications_deployment_success_thread_id, + \App\Notifications\Application\DeploymentFailed::class => $settings->telegram_notifications_deployment_failure_thread_id, + \App\Notifications\Application\StatusChanged::class, + \App\Notifications\Container\ContainerRestarted::class, + \App\Notifications\Container\ContainerStopped::class => $settings->telegram_notifications_status_change_thread_id, + + \App\Notifications\Database\BackupSuccess::class => $settings->telegram_notifications_backup_success_thread_id, + \App\Notifications\Database\BackupFailed::class => $settings->telegram_notifications_backup_failure_thread_id, + + \App\Notifications\ScheduledTask\TaskSuccess::class => $settings->telegram_notifications_scheduled_task_success_thread_id, + \App\Notifications\ScheduledTask\TaskFailed::class => $settings->telegram_notifications_scheduled_task_failure_thread_id, + + \App\Notifications\Server\DockerCleanupSuccess::class => $settings->telegram_notifications_docker_cleanup_success_thread_id, + \App\Notifications\Server\DockerCleanupFailed::class => $settings->telegram_notifications_docker_cleanup_failure_thread_id, + \App\Notifications\Server\HighDiskUsage::class => $settings->telegram_notifications_server_disk_usage_thread_id, + \App\Notifications\Server\Unreachable::class => $settings->telegram_notifications_server_unreachable_thread_id, + \App\Notifications\Server\Reachable::class => $settings->telegram_notifications_server_reachable_thread_id, + + default => null, + }; + if (! $telegramToken || ! $chatId || ! $message) { return; } - SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $topicId); + + SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $threadId); } } diff --git a/app/Notifications/Channels/TransactionalEmailChannel.php b/app/Notifications/Channels/TransactionalEmailChannel.php index cc7d76ebf2..7617802313 100644 --- a/app/Notifications/Channels/TransactionalEmailChannel.php +++ b/app/Notifications/Channels/TransactionalEmailChannel.php @@ -7,7 +7,6 @@ use Illuminate\Mail\Message; use Illuminate\Notifications\Notification; use Illuminate\Support\Facades\Mail; -use Log; class TransactionalEmailChannel { @@ -15,8 +14,6 @@ public function send(User $notifiable, Notification $notification): void { $settings = instanceSettings(); if (! data_get($settings, 'smtp_enabled') && ! data_get($settings, 'resend_enabled')) { - Log::info('SMTP/Resend not enabled'); - return; } $email = $notifiable->email; diff --git a/app/Notifications/Container/ContainerRestarted.php b/app/Notifications/Container/ContainerRestarted.php index eb709535f4..68fc6b019d 100644 --- a/app/Notifications/Container/ContainerRestarted.php +++ b/app/Notifications/Container/ContainerRestarted.php @@ -5,6 +5,8 @@ use App\Models\Server; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; class ContainerRestarted extends CustomEmailNotification @@ -16,7 +18,7 @@ public function __construct(public string $name, public Server $server, public ? public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'status_changes'); + return $notifiable->getEnabledChannels('status_change'); } public function toMail(): MailMessage @@ -66,4 +68,38 @@ public function toTelegram(): array return $payload; } + + public function toPushover(): PushoverMessage + { + $buttons = []; + if ($this->url) { + $buttons[] = [ + 'text' => 'Check Proxy in Coolify', + 'url' => $this->url, + ]; + } + + return new PushoverMessage( + title: 'Resource restarted', + level: 'warning', + message: "A resource ({$this->name}) has been restarted automatically on {$this->server->name}", + buttons: $buttons, + ); + } + + public function toSlack(): SlackMessage + { + $title = 'Resource restarted'; + $description = "A resource ({$this->name}) has been restarted automatically on {$this->server->name}"; + + if ($this->url) { + $description .= "\n**Resource URL:** {$this->url}"; + } + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::warningColor() + ); + } } diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php index a73e984a01..49aea196d4 100644 --- a/app/Notifications/Container/ContainerStopped.php +++ b/app/Notifications/Container/ContainerStopped.php @@ -5,6 +5,8 @@ use App\Models\Server; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; class ContainerStopped extends CustomEmailNotification @@ -16,7 +18,7 @@ public function __construct(public string $name, public Server $server, public ? public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'status_changes'); + return $notifiable->getEnabledChannels('status_change'); } public function toMail(): MailMessage @@ -66,4 +68,39 @@ public function toTelegram(): array return $payload; } + + public function toPushover(): PushoverMessage + { + $buttons = []; + if ($this->url) { + $buttons[] = [ + 'text' => 'Open Application in Coolify', + 'url' => $this->url, + ]; + } + + return new PushoverMessage( + title: 'Resource stopped', + level: 'error', + message: "A resource ({$this->name}) has been stopped unexpectedly on {$this->server->name}", + buttons: $buttons, + ); + } + + + public function toSlack(): SlackMessage + { + $title = 'Resource stopped'; + $description = "A resource ({$this->name}) has been stopped unexpectedly on {$this->server->name}"; + + if ($this->url) { + $description .= "\n**Resource URL:** {$this->url}"; + } + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/Database/BackupFailed.php b/app/Notifications/Database/BackupFailed.php index beeea08042..6dcb705839 100644 --- a/app/Notifications/Database/BackupFailed.php +++ b/app/Notifications/Database/BackupFailed.php @@ -5,6 +5,8 @@ use App\Models\ScheduledDatabaseBackup; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; class BackupFailed extends CustomEmailNotification @@ -22,13 +24,13 @@ public function __construct(ScheduledDatabaseBackup $backup, public $database, p public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'database_backups'); + return $notifiable->getEnabledChannels('backup_failure'); } public function toMail(): MailMessage { $mail = new MailMessage; - $mail->subject("Coolify: [ACTION REQUIRED] Backup FAILED for {$this->database->name}"); + $mail->subject("Coolify: [ACTION REQUIRED] Database Backup FAILED for {$this->database->name}"); $mail->view('emails.backup-failed', [ 'name' => $this->name, 'database_name' => $this->database_name, @@ -62,4 +64,28 @@ public function toTelegram(): array 'message' => $message, ]; } + + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Database backup failed', + level: 'error', + message: "Database backup for {$this->name} (db:{$this->database_name}) was FAILED

Frequency: {$this->frequency} .
Reason: {$this->output}", + ); + } + + public function toSlack(): SlackMessage + { + $title = 'Database backup failed'; + $description = "Database backup for {$this->name} (db:{$this->database_name}) has FAILED."; + + $description .= "\n\n**Frequency:** {$this->frequency}"; + $description .= "\n\n**Error Output:**\n{$this->output}"; + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php index d8bab069be..4c3e8e060d 100644 --- a/app/Notifications/Database/BackupSuccess.php +++ b/app/Notifications/Database/BackupSuccess.php @@ -5,6 +5,8 @@ use App\Models\ScheduledDatabaseBackup; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; class BackupSuccess extends CustomEmailNotification @@ -23,7 +25,7 @@ public function __construct(ScheduledDatabaseBackup $backup, public $database, p public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'database_backups'); + return $notifiable->getEnabledChannels('backup_success'); } public function toMail(): MailMessage @@ -60,4 +62,29 @@ public function toTelegram(): array 'message' => $message, ]; } + + + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Database backup successful', + level: 'success', + message: "Database backup for {$this->name} (db:{$this->database_name}) was successful.

Frequency: {$this->frequency}.", + ); + } + + + public function toSlack(): SlackMessage + { + $title = 'Database backup successful'; + $description = "Database backup for {$this->name} (db:{$this->database_name}) was successful."; + + $description .= "\n\n**Frequency:** {$this->frequency}"; + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::successColor() + ); + } } diff --git a/app/Notifications/Dto/PushoverMessage.php b/app/Notifications/Dto/PushoverMessage.php new file mode 100644 index 0000000000..0efd1d5269 --- /dev/null +++ b/app/Notifications/Dto/PushoverMessage.php @@ -0,0 +1,50 @@ +level) { + 'info' => 'ℹ️', + 'error' => '❌', + 'success' => '✅ ', + 'warning' => '⚠️', + }; + } + + public function toPayload(string $token, string $user): array + { + $levelIcon = $this->getLevelIcon(); + $payload = [ + 'token' => $token, + 'user' => $user, + 'title' => "{$levelIcon} {$this->title}", + 'message' => $this->message, + 'html' => 1, + ]; + + foreach ($this->buttons as $button) { + $buttonUrl = data_get($button, 'url'); + $text = data_get($button, 'text', 'Click here'); + if ($buttonUrl && str_contains($buttonUrl, 'http://localhost')) { + $buttonUrl = str_replace('http://localhost', config('app.url'), $buttonUrl); + } + $payload['message'] .= " " . $text . ''; + } + + Log::info('Pushover message', $payload); + + return $payload; + } +} diff --git a/app/Notifications/Dto/SlackMessage.php b/app/Notifications/Dto/SlackMessage.php new file mode 100644 index 0000000000..879bf65472 --- /dev/null +++ b/app/Notifications/Dto/SlackMessage.php @@ -0,0 +1,32 @@ +getEnabledChannels('general'); } public function toDiscord(): DiscordMessage @@ -51,4 +40,22 @@ public function toTelegram(): array 'message' => $this->message, ]; } + + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'General Notification', + level: 'info', + message: $this->message, + ); + } + + public function toSlack(): SlackMessage + { + return new SlackMessage( + title: 'Coolify: General Notification', + description: $this->message, + color: SlackMessage::infoColor(), + ); + } } diff --git a/app/Notifications/ScheduledTask/TaskFailed.php b/app/Notifications/ScheduledTask/TaskFailed.php index 701f612776..c4d92f2135 100644 --- a/app/Notifications/ScheduledTask/TaskFailed.php +++ b/app/Notifications/ScheduledTask/TaskFailed.php @@ -5,6 +5,8 @@ use App\Models\ScheduledTask; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; class TaskFailed extends CustomEmailNotification @@ -15,15 +17,15 @@ public function __construct(public ScheduledTask $task, public string $output) { $this->onQueue('high'); if ($task->application) { - $this->url = $task->application->failedTaskLink($task->uuid); + $this->url = $task->application->taskLink($task->uuid); } elseif ($task->service) { - $this->url = $task->service->failedTaskLink($task->uuid); + $this->url = $task->service->taskLink($task->uuid); } } public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'scheduled_tasks'); + return $notifiable->getEnabledChannels('scheduled_task_failure'); } public function toMail(): MailMessage @@ -68,4 +70,48 @@ public function toTelegram(): array 'message' => $message, ]; } + + public function toPushover(): PushoverMessage + { + $message = "Scheduled task ({$this->task->name}) failed
"; + + if ($this->output) { + $message .= "
Error Output:{$this->output}"; + } + + $buttons = []; + if ($this->url) { + $buttons[] = [ + 'text' => 'Open task in Coolify', + 'url' => (string) $this->url, + ]; + } + + return new PushoverMessage( + title: 'Scheduled task failed', + level: 'error', + message: $message, + buttons: $buttons, + ); + } + + public function toSlack(): SlackMessage + { + $title = 'Scheduled task failed'; + $description = "Scheduled task ({$this->task->name}) failed."; + + if ($this->output) { + $description .= "\n\n**Error Output:**\n{$this->output}"; + } + + if ($this->url) { + $description .= "\n\n**Task URL:** {$this->url}"; + } + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/ScheduledTask/TaskSuccess.php b/app/Notifications/ScheduledTask/TaskSuccess.php new file mode 100644 index 0000000000..5d4154e7ab --- /dev/null +++ b/app/Notifications/ScheduledTask/TaskSuccess.php @@ -0,0 +1,108 @@ +onQueue('high'); + if ($task->application) { + $this->url = $task->application->taskLink($task->uuid); + } elseif ($task->service) { + $this->url = $task->service->taskLink($task->uuid); + } + } + + public function via(object $notifiable): array + { + return $notifiable->getEnabledChannels('scheduled_task_success'); + } + + public function toMail(): MailMessage + { + $mail = new MailMessage; + $mail->subject("Coolify: Scheduled task ({$this->task->name}) succeeded."); + $mail->view('emails.scheduled-task-success', [ + 'task' => $this->task, + 'url' => $this->url, + 'output' => $this->output, + ]); + + return $mail; + } + + public function toDiscord(): DiscordMessage + { + $message = new DiscordMessage( + title: ':white_check_mark: Scheduled task succeeded', + description: "Scheduled task ({$this->task->name}) succeeded.", + color: DiscordMessage::successColor(), + ); + + if ($this->url) { + $message->addField('Scheduled task', '[Link]('.$this->url.')'); + } + + return $message; + } + + public function toTelegram(): array + { + $message = "Coolify: Scheduled task ({$this->task->name}) succeeded."; + if ($this->url) { + $buttons[] = [ + 'text' => 'Open task in Coolify', + 'url' => (string) $this->url, + ]; + } + + return [ + 'message' => $message, + ]; + } + + public function toPushover(): PushoverMessage + { + $message = "Coolify: Scheduled task ({$this->task->name}) succeeded."; + $buttons = []; + if ($this->url) { + $buttons[] = [ + 'text' => 'Open task in Coolify', + 'url' => (string) $this->url, + ]; + } + + return new PushoverMessage( + title: 'Scheduled task succeeded', + level: 'success', + message: $message, + buttons: $buttons, + ); + } + + public function toSlack(): SlackMessage + { + $title = 'Scheduled task succeeded'; + $description = "Scheduled task ({$this->task->name}) succeeded."; + + if ($this->url) { + $description .= "\n\n**Task URL:** {$this->url}"; + } + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::successColor() + ); + } +} diff --git a/app/Notifications/Server/DockerCleanup.php b/app/Notifications/Server/DockerCleanup.php deleted file mode 100644 index 2d007a262d..0000000000 --- a/app/Notifications/Server/DockerCleanup.php +++ /dev/null @@ -1,65 +0,0 @@ -onQueue('high'); - } - - public function via(object $notifiable): array - { - $channels = []; - // $isEmailEnabled = isEmailEnabled($notifiable); - $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); - $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - - if ($isDiscordEnabled) { - $channels[] = DiscordChannel::class; - } - // if ($isEmailEnabled) { - // $channels[] = EmailChannel::class; - // } - if ($isTelegramEnabled) { - $channels[] = TelegramChannel::class; - } - - return $channels; - } - - // public function toMail(): MailMessage - // { - // $mail = new MailMessage(); - // $mail->subject("Coolify: Server ({$this->server->name}) high disk usage detected!"); - // $mail->view('emails.high-disk-usage', [ - // 'name' => $this->server->name, - // 'disk_usage' => $this->disk_usage, - // 'threshold' => $this->docker_cleanup_threshold, - // ]); - // return $mail; - // } - - public function toDiscord(): DiscordMessage - { - return new DiscordMessage( - title: ':white_check_mark: Server cleanup job done', - description: $this->message, - color: DiscordMessage::successColor(), - ); - } - - public function toTelegram(): array - { - return [ - 'message' => "Coolify: Server '{$this->server->name}' cleanup job done!\n\n{$this->message}", - ]; - } -} diff --git a/app/Notifications/Server/DockerCleanupFailed.php b/app/Notifications/Server/DockerCleanupFailed.php new file mode 100644 index 0000000000..0291eed19d --- /dev/null +++ b/app/Notifications/Server/DockerCleanupFailed.php @@ -0,0 +1,69 @@ +onQueue('high'); + } + + public function via(object $notifiable): array + { + return $notifiable->getEnabledChannels('docker_cleanup_failure'); + } + + public function toMail(): MailMessage + { + $mail = new MailMessage; + $mail->subject("Coolify: [ACTION REQUIRED] Docker cleanup job failed on {$this->server->name}"); + $mail->view('emails.docker-cleanup-failed', [ + 'name' => $this->server->name, + 'text' => $this->message, + ]); + + return $mail; + } + + public function toDiscord(): DiscordMessage + { + return new DiscordMessage( + title: ':cross_mark: Coolify: [ACTION REQUIRED] Docker cleanup job failed on '.$this->server->name, + description: $this->message, + color: DiscordMessage::errorColor(), + ); + } + + public function toTelegram(): array + { + return [ + 'message' => "Coolify: [ACTION REQUIRED] Docker cleanup job failed on {$this->server->name}!\n\n{$this->message}", + ]; + } + + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Docker cleanup job failed', + level: 'error', + message: "[ACTION REQUIRED] Docker cleanup job failed on {$this->server->name}!\n\n{$this->message}", + ); + } + + public function toSlack(): SlackMessage + { + return new SlackMessage( + title: 'Coolify: [ACTION REQUIRED] Docker cleanup job failed', + description: "Docker cleanup job failed on '{$this->server->name}'!\n\n{$this->message}", + color: SlackMessage::errorColor() + ); + } +} diff --git a/app/Notifications/Server/DockerCleanupSuccess.php b/app/Notifications/Server/DockerCleanupSuccess.php new file mode 100644 index 0000000000..1a652d1890 --- /dev/null +++ b/app/Notifications/Server/DockerCleanupSuccess.php @@ -0,0 +1,69 @@ +onQueue('high'); + } + + public function via(object $notifiable): array + { + return $notifiable->getEnabledChannels('docker_cleanup_success'); + } + + public function toMail(): MailMessage + { + $mail = new MailMessage; + $mail->subject("Coolify: Docker cleanup job succeeded on {$this->server->name}"); + $mail->view('emails.docker-cleanup-success', [ + 'name' => $this->server->name, + 'text' => $this->message, + ]); + + return $mail; + } + + public function toDiscord(): DiscordMessage + { + return new DiscordMessage( + title: ':white_check_mark: Coolify: Docker cleanup job succeeded on '.$this->server->name, + description: $this->message, + color: DiscordMessage::successColor(), + ); + } + + public function toTelegram(): array + { + return [ + 'message' => "Coolify: Docker cleanup job succeeded on {$this->server->name}!\n\n{$this->message}", + ]; + } + + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Docker cleanup job succeeded', + level: 'success', + message: "Docker cleanup job succeeded on {$this->server->name}!\n\n{$this->message}", + ); + } + + public function toSlack(): SlackMessage + { + return new SlackMessage( + title: 'Coolify: Docker cleanup job succeeded', + description: "Docker cleanup job succeeded on '{$this->server->name}'!\n\n{$this->message}", + color: SlackMessage::successColor() + ); + } +} diff --git a/app/Notifications/Server/ForceDisabled.php b/app/Notifications/Server/ForceDisabled.php index eabf8b334d..7a1f7bcbf7 100644 --- a/app/Notifications/Server/ForceDisabled.php +++ b/app/Notifications/Server/ForceDisabled.php @@ -3,11 +3,10 @@ namespace App\Notifications\Server; 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 App\Notifications\Dto\PushoverMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; class ForceDisabled extends CustomEmailNotification @@ -19,22 +18,7 @@ public function __construct(public Server $server) public function via(object $notifiable): array { - $channels = []; - $isEmailEnabled = isEmailEnabled($notifiable); - $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); - $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - - if ($isDiscordEnabled) { - $channels[] = DiscordChannel::class; - } - if ($isEmailEnabled) { - $channels[] = EmailChannel::class; - } - if ($isTelegramEnabled) { - $channels[] = TelegramChannel::class; - } - - return $channels; + return $notifiable->getEnabledChannels('server_force_disabled'); } public function toMail(): MailMessage @@ -67,4 +51,27 @@ public function toTelegram(): array 'message' => "Coolify: Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).", ]; } + + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Server disabled', + level: 'error', + message: "Server ({$this->server->name}) disabled because it is not paid!\n All automations and integrations are stopped.
Please update your subscription to enable the server again [here](https://app.coolify.io/subscriptions).", + ); + } + + public function toSlack(): SlackMessage + { + $title = 'Server disabled'; + $description = "Server ({$this->server->name}) disabled because it is not paid!\n"; + $description .= "All automations and integrations are stopped.\n\n"; + $description .= 'Please update your subscription to enable the server again: https://app.coolify.io/subscriptions'; + + return new SlackMessage( + title: $title, + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/Server/ForceEnabled.php b/app/Notifications/Server/ForceEnabled.php index 0c21ed6b8e..36dad3c60f 100644 --- a/app/Notifications/Server/ForceEnabled.php +++ b/app/Notifications/Server/ForceEnabled.php @@ -3,11 +3,10 @@ namespace App\Notifications\Server; 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 App\Notifications\Dto\PushoverMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; class ForceEnabled extends CustomEmailNotification @@ -19,22 +18,7 @@ public function __construct(public Server $server) public function via(object $notifiable): array { - $channels = []; - $isEmailEnabled = isEmailEnabled($notifiable); - $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); - $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - - if ($isDiscordEnabled) { - $channels[] = DiscordChannel::class; - } - if ($isEmailEnabled) { - $channels[] = EmailChannel::class; - } - if ($isTelegramEnabled) { - $channels[] = TelegramChannel::class; - } - - return $channels; + return $notifiable->getEnabledChannels('server_force_enabled'); } public function toMail(): MailMessage @@ -63,4 +47,22 @@ public function toTelegram(): array 'message' => "Coolify: Server ({$this->server->name}) enabled again!", ]; } + + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Server enabled', + level: 'success', + message: "Server ({$this->server->name}) enabled again!", + ); + } + + public function toSlack(): SlackMessage + { + return new SlackMessage( + title: 'Server enabled', + description: "Server '{$this->server->name}' enabled again!", + color: SlackMessage::successColor() + ); + } } diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php index 7cec2e8929..aea9abd031 100644 --- a/app/Notifications/Server/HighDiskUsage.php +++ b/app/Notifications/Server/HighDiskUsage.php @@ -5,6 +5,8 @@ use App\Models\Server; use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; class HighDiskUsage extends CustomEmailNotification @@ -16,7 +18,7 @@ public function __construct(public Server $server, public int $disk_usage, publi public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'server_disk_usage'); + return $notifiable->getEnabledChannels('server_disk_usage'); } public function toMail(): MailMessage @@ -55,4 +57,35 @@ public function toTelegram(): array 'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->server_disk_usage_notification_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.", ]; } + + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'High disk usage detected', + level: 'warning', + message: "Server '{$this->server->name}' high disk usage detected!

Disk usage: {$this->disk_usage}%.
Threshold: {$this->server_disk_usage_notification_threshold}%.
Please cleanup your disk to prevent data-loss.", + buttons: [ + 'Change settings' => base_url().'/server/'.$this->server->uuid."#advanced", + 'Tips for cleanup' => "https://coolify.io/docs/knowledge-base/server/automated-cleanup", + ], + ); + } + + public function toSlack(): SlackMessage + { + $description = "Server '{$this->server->name}' high disk usage detected!\n"; + $description .= "Disk usage: {$this->disk_usage}%\n"; + $description .= "Threshold: {$this->server_disk_usage_notification_threshold}%\n\n"; + $description .= "Please cleanup your disk to prevent data-loss.\n"; + $description .= "Tips for cleanup: https://coolify.io/docs/knowledge-base/server/automated-cleanup\n"; + $description .= "Change settings:\n"; + $description .= '- Threshold: '.base_url().'/server/'.$this->server->uuid."#advanced\n"; + $description .= '- Notifications: '.base_url().'/notifications/discord'; + + return new SlackMessage( + title: 'High disk usage detected', + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/Server/Reachable.php b/app/Notifications/Server/Reachable.php index 44189c3b5e..e03aef6b79 100644 --- a/app/Notifications/Server/Reachable.php +++ b/app/Notifications/Server/Reachable.php @@ -3,11 +3,10 @@ namespace App\Notifications\Server; 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 App\Notifications\Dto\PushoverMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; class Reachable extends CustomEmailNotification @@ -28,22 +27,7 @@ public function via(object $notifiable): array return []; } - $channels = []; - $isEmailEnabled = isEmailEnabled($notifiable); - $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); - $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - - if ($isDiscordEnabled) { - $channels[] = DiscordChannel::class; - } - if ($isEmailEnabled) { - $channels[] = EmailChannel::class; - } - if ($isTelegramEnabled) { - $channels[] = TelegramChannel::class; - } - - return $channels; + return $notifiable->getEnabledChannels('server_reachable'); } public function toMail(): MailMessage @@ -66,10 +50,28 @@ public function toDiscord(): DiscordMessage ); } + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Server revived', + message: "Server '{$this->server->name}' revived. All automations & integrations are turned on again!", + level: 'success', + ); + } + public function toTelegram(): array { return [ 'message' => "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!", ]; } + + public function toSlack(): SlackMessage + { + return new SlackMessage( + title: 'Server revived', + description: "Server '{$this->server->name}' revived.\nAll automations & integrations are turned on again!", + color: SlackMessage::successColor() + ); + } } diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php index 6fb792bdc9..fe90cc6105 100644 --- a/app/Notifications/Server/Unreachable.php +++ b/app/Notifications/Server/Unreachable.php @@ -3,11 +3,10 @@ namespace App\Notifications\Server; 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 App\Notifications\Dto\PushoverMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Notifications\Messages\MailMessage; class Unreachable extends CustomEmailNotification @@ -28,22 +27,7 @@ public function via(object $notifiable): array return []; } - $channels = []; - $isEmailEnabled = isEmailEnabled($notifiable); - $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); - $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - - if ($isDiscordEnabled) { - $channels[] = DiscordChannel::class; - } - if ($isEmailEnabled) { - $channels[] = EmailChannel::class; - } - if ($isTelegramEnabled) { - $channels[] = TelegramChannel::class; - } - - return $channels; + return $notifiable->getEnabledChannels('server_unreachable'); } public function toMail(): ?MailMessage @@ -76,4 +60,26 @@ public function toTelegram(): ?array 'message' => "Coolify: Your server '{$this->server->name}' is unreachable. All automations & integrations are turned off! Please check your server! IMPORTANT: We automatically try to revive your server and turn on all automations & integrations.", ]; } + + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Server unreachable', + level: 'error', + message: "Your server '{$this->server->name}' is unreachable.
All automations & integrations are turned off!

IMPORTANT: We automatically try to revive your server and turn on all automations & integrations.", + ); + } + + public function toSlack(): SlackMessage + { + $description = "Your server '{$this->server->name}' is unreachable.\n"; + $description .= "All automations & integrations are turned off!\n\n"; + $description .= '*IMPORTANT:* We automatically try to revive your server and turn on all automations & integrations.'; + + return new SlackMessage( + title: 'Server unreachable', + description: $description, + color: SlackMessage::errorColor() + ); + } } diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index 64f9bb0a55..65971a0eee 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -2,7 +2,14 @@ namespace App\Notifications; +use App\Notifications\Channels\DiscordChannel; +use App\Notifications\Channels\EmailChannel; +use App\Notifications\Channels\SlackChannel; +use App\Notifications\Channels\TelegramChannel; +use App\Notifications\Channels\PushoverChannel; use App\Notifications\Dto\DiscordMessage; +use App\Notifications\Dto\PushoverMessage; +use App\Notifications\Dto\SlackMessage; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; @@ -15,20 +22,33 @@ class Test extends Notification implements ShouldQueue public $tries = 5; - public function __construct(public ?string $emails = null) + public function __construct(public ?string $emails = null, public ?string $channel = null) { $this->onQueue('high'); } public function via(object $notifiable): array { - return setNotificationChannels($notifiable, 'test'); + if ($this->channel) { + $channels = match ($this->channel) { + 'email' => [EmailChannel::class], + 'discord' => [DiscordChannel::class], + 'telegram' => [TelegramChannel::class], + 'slack' => [SlackChannel::class], + 'pushover' => [PushoverChannel::class], + default => [], + }; + } else { + $channels = $notifiable->getEnabledChannels('test'); + } + + return $channels; } public function middleware(object $notifiable, string $channel) { return match ($channel) { - \App\Notifications\Channels\EmailChannel::class => [new RateLimited('email')], + EmailChannel::class => [new RateLimited('email')], default => [], }; } @@ -67,4 +87,26 @@ public function toTelegram(): array ], ]; } + + public function toPushover(): PushoverMessage + { + return new PushoverMessage( + title: 'Test Pushover Notification', + message: 'This is a test Pushover notification from Coolify.', + buttons: [ + [ + 'text' => 'Go to your dashboard', + 'url' => base_url(), + ], + ], + ); + } + + public function toSlack(): SlackMessage + { + return new SlackMessage( + title: 'Test Slack Notification', + description: 'This is a test Slack notification from Coolify.' + ); + } } diff --git a/app/Providers/EventServiceProvider.php b/app/Providers/EventServiceProvider.php index 7ba72e10d4..aa3579f8da 100644 --- a/app/Providers/EventServiceProvider.php +++ b/app/Providers/EventServiceProvider.php @@ -9,6 +9,9 @@ use Illuminate\Foundation\Events\MaintenanceModeDisabled; use Illuminate\Foundation\Events\MaintenanceModeEnabled; use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider; +use SocialiteProviders\Authentik\AuthentikExtendSocialite; +use SocialiteProviders\Azure\AzureExtendSocialite; +use SocialiteProviders\Manager\SocialiteWasCalled; class EventServiceProvider extends ServiceProvider { @@ -19,8 +22,9 @@ class EventServiceProvider extends ServiceProvider MaintenanceModeDisabled::class => [ MaintenanceModeDisabledNotification::class, ], - \SocialiteProviders\Manager\SocialiteWasCalled::class => [ - \SocialiteProviders\Azure\AzureExtendSocialite::class.'@handle', + SocialiteWasCalled::class => [ + AzureExtendSocialite::class.'@handle', + AuthentikExtendSocialite::class.'@handle', ], ProxyStarted::class => [ ProxyStartedNotification::class, diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index bbbf483454..ed27a158a1 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -50,13 +50,10 @@ public function boot(): void if (! $settings->is_registration_enabled) { return redirect()->route('login'); } - if (config('constants.waitlist.enabled')) { - return redirect()->route('waitlist.index'); - } else { - return view('auth.register', [ - 'isFirstUser' => $isFirstUser, - ]); - } + + return view('auth.register', [ + 'isFirstUser' => $isFirstUser, + ]); }); Fortify::loginView(function () { diff --git a/app/Traits/HasNotificationSettings.php b/app/Traits/HasNotificationSettings.php new file mode 100644 index 0000000000..ef858d0b6e --- /dev/null +++ b/app/Traits/HasNotificationSettings.php @@ -0,0 +1,93 @@ + $this->emailNotificationSettings, + 'discord' => $this->discordNotificationSettings, + 'telegram' => $this->telegramNotificationSettings, + 'slack' => $this->slackNotificationSettings, + 'pushover' => $this->pushoverNotificationSettings, + default => null, + }; + } + + /** + * Check if a notification channel is enabled + */ + public function isNotificationEnabled(string $channel): bool + { + $settings = $this->getNotificationSettings($channel); + + return $settings?->isEnabled() ?? false; + } + + /** + * Check if a specific notification type is enabled for a channel + */ + public function isNotificationTypeEnabled(string $channel, string $event): bool + { + $settings = $this->getNotificationSettings($channel); + + if (! $settings || ! $this->isNotificationEnabled($channel)) { + return false; + } + + if (in_array($event, $this->alwaysSendEvents)) { + return true; + } + + $settingKey = "{$event}_{$channel}_notifications"; + + return (bool) $settings->$settingKey; + } + + /** + * Get all enabled notification channels for an event + */ + public function getEnabledChannels(string $event): array + { + $channels = []; + + $channelMap = [ + 'email' => EmailChannel::class, + 'discord' => DiscordChannel::class, + 'telegram' => TelegramChannel::class, + 'slack' => SlackChannel::class, + 'pushover' => PushoverChannel::class, + ]; + + if ($event === 'general') { + unset($channelMap['email']); + } + + foreach ($channelMap as $channel => $channelClass) { + if ($this->isNotificationEnabled($channel) && $this->isNotificationTypeEnabled($channel, $event)) { + $channels[] = $channelClass; + } + } + + return $channels; + } +} diff --git a/app/View/Components/Forms/Checkbox.php b/app/View/Components/Forms/Checkbox.php index 0bdebe7e44..e46598e8e9 100644 --- a/app/View/Components/Forms/Checkbox.php +++ b/app/View/Components/Forms/Checkbox.php @@ -15,6 +15,7 @@ public function __construct( public ?string $id = null, public ?string $name = null, public ?string $value = null, + public ?string $domValue = null, public ?string $label = null, public ?string $helper = null, public string|bool|null $checked = false, diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 8dd01a1624..eda2133a75 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -288,9 +288,9 @@ 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"; + $handle = 'handle_path'; + if (! $is_stripprefix_enabled) { + $handle = 'handle'; } if (is_null($port) && ! is_null($onlyPort)) { $port = $onlyPort; @@ -302,7 +302,6 @@ 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}.{$loop}_reverse_proxy={{upstreams $port}}"); } else { diff --git a/bootstrap/helpers/notifications.php b/bootstrap/helpers/notifications.php new file mode 100644 index 0000000000..3b1eb758b8 --- /dev/null +++ b/bootstrap/helpers/notifications.php @@ -0,0 +1,87 @@ +smtp_enabled || $settings->resend_enabled; +} + +function send_internal_notification(string $message): void +{ + try { + $team = Team::find(0); + $team?->notify(new GeneralNotification($message)); + } catch (\Throwable $e) { + ray($e->getMessage()); + } +} + +function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null): void +{ + $settings = instanceSettings(); + $type = set_transanctional_email_settings($settings); + if (! $type) { + throw new Exception('No email settings found.'); + } + if ($cc) { + Mail::send( + [], + [], + fn (Message $message) => $message + ->to($email) + ->replyTo($email) + ->cc($cc) + ->subject($mail->subject) + ->html((string) $mail->render()) + ); + } else { + Mail::send( + [], + [], + fn (Message $message) => $message + ->to($email) + ->subject($mail->subject) + ->html((string) $mail->render()) + ); + } +} + +function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string // +{ + if (! $settings) { + $settings = instanceSettings(); + } + config()->set('mail.from.address', data_get($settings, 'smtp_from_address')); + config()->set('mail.from.name', data_get($settings, 'smtp_from_name')); + if (data_get($settings, 'resend_enabled')) { + config()->set('mail.default', 'resend'); + config()->set('resend.api_key', data_get($settings, 'resend_api_key')); + + return 'resend'; + } + if (data_get($settings, 'smtp_enabled')) { + config()->set('mail.default', 'smtp'); + config()->set('mail.mailers.smtp', [ + 'transport' => 'smtp', + 'host' => data_get($settings, 'smtp_host'), + 'port' => data_get($settings, 'smtp_port'), + 'encryption' => data_get($settings, 'smtp_encryption'), + 'username' => data_get($settings, 'smtp_username'), + 'password' => data_get($settings, 'smtp_password'), + 'timeout' => data_get($settings, 'smtp_timeout'), + 'local_domain' => null, + ]); + + return 'smtp'; + } + + return null; +} diff --git a/bootstrap/helpers/proxy.php b/bootstrap/helpers/proxy.php index a8ef0fe5a7..463e89b6f8 100644 --- a/bootstrap/helpers/proxy.php +++ b/bootstrap/helpers/proxy.php @@ -173,13 +173,12 @@ function generate_default_proxy_configuration(Server $server) ], 'volumes' => [ '/var/run/docker.sock:/var/run/docker.sock:ro', - "{$proxy_path}:/traefik", + ], 'command' => [ '--ping=true', '--ping.entrypoint=http', '--api.dashboard=true', - '--api.insecure=false', '--entrypoints.http.address=:80', '--entrypoints.https.address=:443', '--entrypoints.http.http.encodequerysemicolons=true', @@ -187,21 +186,26 @@ function generate_default_proxy_configuration(Server $server) '--entrypoints.https.http.encodequerysemicolons=true', '--entryPoints.https.http2.maxConcurrentStreams=50', '--entrypoints.https.http3', - '--providers.docker.exposedbydefault=false', '--providers.file.directory=/traefik/dynamic/', + '--providers.docker.exposedbydefault=false', '--providers.file.watch=true', '--certificatesresolvers.letsencrypt.acme.httpchallenge=true', - '--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json', '--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=http', + '--certificatesresolvers.letsencrypt.acme.storage=/traefik/acme.json', ], 'labels' => $labels, ], ], ]; if (isDev()) { - // $config['services']['traefik']['command'][] = "--log.level=debug"; + $config['services']['traefik']['command'][] = '--api.insecure=true'; + $config['services']['traefik']['command'][] = '--log.level=debug'; $config['services']['traefik']['command'][] = '--accesslog.filepath=/traefik/access.log'; $config['services']['traefik']['command'][] = '--accesslog.bufferingsize=100'; + $config['services']['traefik']['volumes'][] = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/proxy/:/traefik'; + } else { + $config['services']['traefik']['command'][] = '--api.insecure=false'; + $config['services']['traefik']['volumes'][] = "{$proxy_path}:/traefik"; } if ($server->isSwarm()) { data_forget($config, 'services.traefik.container_name'); diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index a3ef93dfc3..7aa411cd10 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -25,22 +25,15 @@ use App\Models\StandaloneRedis; use App\Models\Team; use App\Models\User; -use App\Notifications\Channels\DiscordChannel; -use App\Notifications\Channels\EmailChannel; -use App\Notifications\Channels\TelegramChannel; -use App\Notifications\Internal\GeneralNotification; use Carbon\CarbonImmutable; use DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException; use Illuminate\Database\UniqueConstraintViolationException; -use Illuminate\Mail\Message; -use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Process\Pool; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Http; -use Illuminate\Support\Facades\Mail; use Illuminate\Support\Facades\Process; use Illuminate\Support\Facades\RateLimiter; use Illuminate\Support\Facades\Request; @@ -266,43 +259,6 @@ function generate_application_name(string $git_repository, string $git_branch, ? return Str::kebab("$git_repository:$git_branch-$cuid"); } -function is_transactional_emails_active(): bool -{ - return isEmailEnabled(\App\Models\InstanceSettings::get()); -} - -function set_transanctional_email_settings(?InstanceSettings $settings = null): ?string -{ - if (! $settings) { - $settings = instanceSettings(); - } - config()->set('mail.from.address', data_get($settings, 'smtp_from_address')); - config()->set('mail.from.name', data_get($settings, 'smtp_from_name')); - if (data_get($settings, 'resend_enabled')) { - config()->set('mail.default', 'resend'); - config()->set('resend.api_key', data_get($settings, 'resend_api_key')); - - return 'resend'; - } - if (data_get($settings, 'smtp_enabled')) { - config()->set('mail.default', 'smtp'); - config()->set('mail.mailers.smtp', [ - 'transport' => 'smtp', - 'host' => data_get($settings, 'smtp_host'), - 'port' => data_get($settings, 'smtp_port'), - 'encryption' => data_get($settings, 'smtp_encryption'), - 'username' => data_get($settings, 'smtp_username'), - 'password' => data_get($settings, 'smtp_password'), - 'timeout' => data_get($settings, 'smtp_timeout'), - 'local_domain' => null, - ]); - - return 'smtp'; - } - - return null; -} - function base_ip(): string { if (isDev()) { @@ -413,80 +369,7 @@ function validate_timezone(string $timezone): bool { return in_array($timezone, timezone_identifiers_list()); } -function send_internal_notification(string $message): void -{ - try { - $team = Team::find(0); - $team?->notify(new GeneralNotification($message)); - } catch (\Throwable $e) { - ray($e->getMessage()); - } -} -function send_user_an_email(MailMessage $mail, string $email, ?string $cc = null): void -{ - $settings = instanceSettings(); - $type = set_transanctional_email_settings($settings); - if (! $type) { - throw new Exception('No email settings found.'); - } - if ($cc) { - Mail::send( - [], - [], - fn (Message $message) => $message - ->to($email) - ->replyTo($email) - ->cc($cc) - ->subject($mail->subject) - ->html((string) $mail->render()) - ); - } else { - Mail::send( - [], - [], - fn (Message $message) => $message - ->to($email) - ->subject($mail->subject) - ->html((string) $mail->render()) - ); - } -} -function isTestEmailEnabled($notifiable) -{ - if (data_get($notifiable, 'use_instance_email_settings') && isInstanceAdmin()) { - return true; - } elseif (data_get($notifiable, 'smtp_enabled') || data_get($notifiable, 'resend_enabled') && auth()->user()->isAdminFromSession()) { - return true; - } - return false; -} -function isEmailEnabled($notifiable) -{ - return data_get($notifiable, 'smtp_enabled') || data_get($notifiable, 'resend_enabled') || data_get($notifiable, 'use_instance_email_settings'); -} -function setNotificationChannels($notifiable, $event) -{ - $channels = []; - $isEmailEnabled = isEmailEnabled($notifiable); - $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); - $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); - $isSubscribedToEmailEvent = data_get($notifiable, "smtp_notifications_$event"); - $isSubscribedToDiscordEvent = data_get($notifiable, "discord_notifications_$event"); - $isSubscribedToTelegramEvent = data_get($notifiable, "telegram_notifications_$event"); - - if ($isDiscordEnabled && $isSubscribedToDiscordEvent) { - $channels[] = DiscordChannel::class; - } - if ($isEmailEnabled && $isSubscribedToEmailEvent) { - $channels[] = EmailChannel::class; - } - if ($isTelegramEnabled && $isSubscribedToTelegramEvent) { - $channels[] = TelegramChannel::class; - } - - return $channels; -} function parseEnvFormatToArray($env_file_contents) { $env_array = []; @@ -941,6 +824,12 @@ function generateEnvValue(string $command, Service|Application|null $service = n case 'PASSWORD_64': $generatedValue = Str::password(length: 64, symbols: false); break; + case 'PASSWORDWITHSYMBOLS': + $generatedValue = Str::password(symbols: true); + break; + case 'PASSWORDWITHSYMBOLS_64': + $generatedValue = Str::password(length: 64, symbols: true); + break; // This is not base64, it's just a random string case 'BASE64_64': $generatedValue = Str::random(64); diff --git a/bootstrap/helpers/socialite.php b/bootstrap/helpers/socialite.php index cad9de7fa8..130227e815 100644 --- a/bootstrap/helpers/socialite.php +++ b/bootstrap/helpers/socialite.php @@ -18,6 +18,17 @@ function get_socialite_provider(string $provider) return Socialite::driver('azure')->setConfig($azure_config); } + if ($provider == 'authentik') { + $authentik_config = new \SocialiteProviders\Manager\Config( + $oauth_setting->client_id, + $oauth_setting->client_secret, + $oauth_setting->redirect_uri, + ['base_url' => $oauth_setting->base_url], + ); + + return Socialite::driver('authentik')->setConfig($authentik_config); + } + $config = [ 'client_id' => $oauth_setting->client_id, 'client_secret' => $oauth_setting->client_secret, diff --git a/bootstrap/helpers/subscriptions.php b/bootstrap/helpers/subscriptions.php index 8ddb1331c6..ab9ee9b9d6 100644 --- a/bootstrap/helpers/subscriptions.php +++ b/bootstrap/helpers/subscriptions.php @@ -67,7 +67,6 @@ function allowedPathsForUnsubscribedAccounts() 'subscription/new', 'login', 'logout', - 'waitlist', 'force-password-reset', 'livewire/update', ]; diff --git a/composer.json b/composer.json index 694bad8822..b8dc354c32 100644 --- a/composer.json +++ b/composer.json @@ -13,7 +13,7 @@ "require": { "php": "^8.2", "3sidedcube/laravel-redoc": "^1.0", - "danharrin/livewire-rate-limiting": "^1.1", + "danharrin/livewire-rate-limiting": "2.0.0", "doctrine/dbal": "^4.2", "guzzlehttp/guzzle": "^7.5.0", "laravel/fortify": "^1.16.0", @@ -39,6 +39,7 @@ "pusher/pusher-php-server": "^7.2", "resend/resend-laravel": "^0.15.0", "sentry/sentry-laravel": "^4.6", + "socialiteproviders/authentik": "^5.2", "socialiteproviders/microsoft-azure": "^5.1", "spatie/laravel-activitylog": "^4.7.3", "spatie/laravel-data": "^4.11", diff --git a/composer.lock b/composer.lock index 8ea0d9a5a4..3fbe72afb4 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": "f50de759f43a3eefb58ce9ebbb02d33b", + "content-hash": "871067cb42e6347ca53ff36e81ac5079", "packages": [ { "name": "3sidedcube/laravel-redoc", @@ -979,16 +979,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.327.1", + "version": "3.334.3", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "3d52ec587989b136e486f94eff3dd316465aeb42" + "reference": "6576a9fcfc6ae7c76aed3c6fa4c3864060f72d04" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3d52ec587989b136e486f94eff3dd316465aeb42", - "reference": "3d52ec587989b136e486f94eff3dd316465aeb42", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6576a9fcfc6ae7c76aed3c6fa4c3864060f72d04", + "reference": "6576a9fcfc6ae7c76aed3c6fa4c3864060f72d04", "shasum": "" }, "require": { @@ -1017,8 +1017,8 @@ "nette/neon": "^2.3", "paragonie/random_compat": ">= 2", "phpunit/phpunit": "^5.6.3 || ^8.5 || ^9.5", - "psr/cache": "^1.0", - "psr/simple-cache": "^1.0", + "psr/cache": "^1.0 || ^2.0 || ^3.0", + "psr/simple-cache": "^1.0 || ^2.0 || ^3.0", "sebastian/comparator": "^1.2.3 || ^4.0", "yoast/phpunit-polyfills": "^1.0" }, @@ -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.327.1" + "source": "https://github.com/aws/aws-sdk-php/tree/3.334.3" }, - "time": "2024-11-15T01:53:30+00:00" + "time": "2024-12-10T19:41:55+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1260,16 +1260,16 @@ }, { "name": "danharrin/livewire-rate-limiting", - "version": "v1.3.1", + "version": "v2.0.0", "source": { "type": "git", "url": "https://github.com/danharrin/livewire-rate-limiting.git", - "reference": "1a1b299e20de61f88ed6e94ea0bbcfc33aab1ddb" + "reference": "0d9c1890174b3d1857dba6ce76de7c178fe20283" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/danharrin/livewire-rate-limiting/zipball/1a1b299e20de61f88ed6e94ea0bbcfc33aab1ddb", - "reference": "1a1b299e20de61f88ed6e94ea0bbcfc33aab1ddb", + "url": "https://api.github.com/repos/danharrin/livewire-rate-limiting/zipball/0d9c1890174b3d1857dba6ce76de7c178fe20283", + "reference": "0d9c1890174b3d1857dba6ce76de7c178fe20283", "shasum": "" }, "require": { @@ -1310,7 +1310,7 @@ "type": "github" } ], - "time": "2024-05-06T09:10:03+00:00" + "time": "2024-11-24T16:57:47+00:00" }, { "name": "dasprid/enum", @@ -1591,29 +1591,27 @@ }, { "name": "doctrine/deprecations", - "version": "1.1.3", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", - "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/31610dbb31faa98e6b5447b62340826f54fbc4e9", + "reference": "31610dbb31faa98e6b5447b62340826f54fbc4e9", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9", - "phpstan/phpstan": "1.4.10 || 1.10.15", - "phpstan/phpstan-phpunit": "^1.0", + "doctrine/coding-standard": "^9 || ^12", + "phpstan/phpstan": "1.4.10 || 2.0.3", + "phpstan/phpstan-phpunit": "^1.0 || ^2", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psalm/plugin-phpunit": "0.18.4", - "psr/log": "^1 || ^2 || ^3", - "vimeo/psalm": "4.30.0 || 5.12.0" + "psr/log": "^1 || ^2 || ^3" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -1621,7 +1619,7 @@ "type": "library", "autoload": { "psr-4": { - "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + "Doctrine\\Deprecations\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -1632,9 +1630,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/1.1.3" + "source": "https://github.com/doctrine/deprecations/tree/1.1.4" }, - "time": "2024-01-30T19:34:25+00:00" + "time": "2024-12-07T21:18:45+00:00" }, { "name": "doctrine/inflector", @@ -1938,16 +1936,16 @@ }, { "name": "firebase/php-jwt", - "version": "v6.10.1", + "version": "v6.10.2", "source": { "type": "git", "url": "https://github.com/firebase/php-jwt.git", - "reference": "500501c2ce893c824c801da135d02661199f60c5" + "reference": "30c19ed0f3264cb660ea496895cfb6ef7ee3653b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/firebase/php-jwt/zipball/500501c2ce893c824c801da135d02661199f60c5", - "reference": "500501c2ce893c824c801da135d02661199f60c5", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/30c19ed0f3264cb660ea496895cfb6ef7ee3653b", + "reference": "30c19ed0f3264cb660ea496895cfb6ef7ee3653b", "shasum": "" }, "require": { @@ -1995,9 +1993,9 @@ ], "support": { "issues": "https://github.com/firebase/php-jwt/issues", - "source": "https://github.com/firebase/php-jwt/tree/v6.10.1" + "source": "https://github.com/firebase/php-jwt/tree/v6.10.2" }, - "time": "2024-05-18T18:05:11+00:00" + "time": "2024-11-24T11:22:49+00:00" }, { "name": "fruitcake/php-cors", @@ -2545,28 +2543,28 @@ }, { "name": "jean85/pretty-package-versions", - "version": "2.0.6", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/Jean85/pretty-package-versions.git", - "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4" + "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/f9fdd29ad8e6d024f52678b570e5593759b550b4", - "reference": "f9fdd29ad8e6d024f52678b570e5593759b550b4", + "url": "https://api.github.com/repos/Jean85/pretty-package-versions/zipball/3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", + "reference": "3c4e5f62ba8d7de1734312e4fff32f67a8daaf10", "shasum": "" }, "require": { - "composer-runtime-api": "^2.0.0", - "php": "^7.1|^8.0" + "composer-runtime-api": "^2.1.0", + "php": "^7.4|^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "jean85/composer-provided-replaced-stub-package": "^1.0", "phpstan/phpstan": "^1.4", - "phpunit/phpunit": "^7.5|^8.5|^9.4", - "vimeo/psalm": "^4.3" + "phpunit/phpunit": "^7.5|^8.5|^9.6", + "vimeo/psalm": "^4.3 || ^5.0" }, "type": "library", "extra": { @@ -2598,9 +2596,9 @@ ], "support": { "issues": "https://github.com/Jean85/pretty-package-versions/issues", - "source": "https://github.com/Jean85/pretty-package-versions/tree/2.0.6" + "source": "https://github.com/Jean85/pretty-package-versions/tree/2.1.0" }, - "time": "2024-03-08T09:58:59+00:00" + "time": "2024-11-18T16:19:46+00:00" }, { "name": "kelunik/certificate", @@ -2662,16 +2660,16 @@ }, { "name": "laravel/fortify", - "version": "v1.24.5", + "version": "v1.25.1", "source": { "type": "git", "url": "https://github.com/laravel/fortify.git", - "reference": "bba8c2ecc3fcc78e8632e0d719ae10bef6343eef" + "reference": "5022e7c01385fd6edcef91c12b19071f8f20d6d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/fortify/zipball/bba8c2ecc3fcc78e8632e0d719ae10bef6343eef", - "reference": "bba8c2ecc3fcc78e8632e0d719ae10bef6343eef", + "url": "https://api.github.com/repos/laravel/fortify/zipball/5022e7c01385fd6edcef91c12b19071f8f20d6d8", + "reference": "5022e7c01385fd6edcef91c12b19071f8f20d6d8", "shasum": "" }, "require": { @@ -2690,13 +2688,13 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - }, "laravel": { "providers": [ "Laravel\\Fortify\\FortifyServiceProvider" ] + }, + "branch-alias": { + "dev-master": "1.x-dev" } }, "autoload": { @@ -2723,27 +2721,27 @@ "issues": "https://github.com/laravel/fortify/issues", "source": "https://github.com/laravel/fortify" }, - "time": "2024-11-12T14:51:12+00:00" + "time": "2024-11-27T14:51:15+00:00" }, { "name": "laravel/framework", - "version": "v11.31.0", + "version": "v11.35.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "365090ed2c68244e3141cdb5e247cdf3dfba2c40" + "reference": "f1a7aaa3c1235b7a95ccaa58db90e0cd9d8c3fcc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/365090ed2c68244e3141cdb5e247cdf3dfba2c40", - "reference": "365090ed2c68244e3141cdb5e247cdf3dfba2c40", + "url": "https://api.github.com/repos/laravel/framework/zipball/f1a7aaa3c1235b7a95ccaa58db90e0cd9d8c3fcc", + "reference": "f1a7aaa3c1235b7a95ccaa58db90e0cd9d8c3fcc", "shasum": "" }, "require": { "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", "composer-runtime-api": "^2.2", "doctrine/inflector": "^2.0.5", - "dragonmantank/cron-expression": "^3.3.2", + "dragonmantank/cron-expression": "^3.4", "egulias/email-validator": "^3.2.1|^4.0", "ext-ctype": "*", "ext-filter": "*", @@ -2753,35 +2751,37 @@ "ext-session": "*", "ext-tokenizer": "*", "fruitcake/php-cors": "^1.3", - "guzzlehttp/guzzle": "^7.8", + "guzzlehttp/guzzle": "^7.8.2", "guzzlehttp/uri-template": "^1.0", "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", - "laravel/serializable-closure": "^1.3", + "laravel/serializable-closure": "^1.3|^2.0", "league/commonmark": "^2.2.1", - "league/flysystem": "^3.8.0", + "league/flysystem": "^3.25.1", + "league/flysystem-local": "^3.25.1", + "league/uri": "^7.5.1", "monolog/monolog": "^3.0", - "nesbot/carbon": "^2.72.2|^3.0", + "nesbot/carbon": "^2.72.2|^3.4", "nunomaduro/termwind": "^2.0", "php": "^8.2", "psr/container": "^1.1.1|^2.0.1", "psr/log": "^1.0|^2.0|^3.0", "psr/simple-cache": "^1.0|^2.0|^3.0", "ramsey/uuid": "^4.7", - "symfony/console": "^7.0", - "symfony/error-handler": "^7.0", - "symfony/finder": "^7.0", - "symfony/http-foundation": "^7.0", - "symfony/http-kernel": "^7.0", - "symfony/mailer": "^7.0", - "symfony/mime": "^7.0", - "symfony/polyfill-php83": "^1.28", - "symfony/process": "^7.0", - "symfony/routing": "^7.0", - "symfony/uid": "^7.0", - "symfony/var-dumper": "^7.0", + "symfony/console": "^7.0.3", + "symfony/error-handler": "^7.0.3", + "symfony/finder": "^7.0.3", + "symfony/http-foundation": "^7.0.3", + "symfony/http-kernel": "^7.0.3", + "symfony/mailer": "^7.0.3", + "symfony/mime": "^7.0.3", + "symfony/polyfill-php83": "^1.31", + "symfony/process": "^7.0.3", + "symfony/routing": "^7.0.3", + "symfony/uid": "^7.0.3", + "symfony/var-dumper": "^7.0.3", "tijsverkoyen/css-to-inline-styles": "^2.2.5", - "vlucas/phpdotenv": "^5.4.1", - "voku/portable-ascii": "^2.0" + "vlucas/phpdotenv": "^5.6.1", + "voku/portable-ascii": "^2.0.2" }, "conflict": { "mockery/mockery": "1.6.8", @@ -2831,29 +2831,32 @@ }, "require-dev": { "ably/ably-php": "^1.0", - "aws/aws-sdk-php": "^3.235.5", + "aws/aws-sdk-php": "^3.322.9", "ext-gmp": "*", - "fakerphp/faker": "^1.23", - "league/flysystem-aws-s3-v3": "^3.0", - "league/flysystem-ftp": "^3.0", - "league/flysystem-path-prefixing": "^3.3", - "league/flysystem-read-only": "^3.3", - "league/flysystem-sftp-v3": "^3.0", - "mockery/mockery": "^1.6", - "nyholm/psr7": "^1.2", - "orchestra/testbench-core": "^9.5", - "pda/pheanstalk": "^5.0", + "fakerphp/faker": "^1.24", + "guzzlehttp/promises": "^2.0.3", + "guzzlehttp/psr7": "^2.4", + "league/flysystem-aws-s3-v3": "^3.25.1", + "league/flysystem-ftp": "^3.25.1", + "league/flysystem-path-prefixing": "^3.25.1", + "league/flysystem-read-only": "^3.25.1", + "league/flysystem-sftp-v3": "^3.25.1", + "mockery/mockery": "^1.6.10", + "orchestra/testbench-core": "^9.6", + "pda/pheanstalk": "^5.0.6", + "php-http/discovery": "^1.15", "phpstan/phpstan": "^1.11.5", - "phpunit/phpunit": "^10.5|^11.0", - "predis/predis": "^2.0.2", + "phpunit/phpunit": "^10.5.35|^11.3.6", + "predis/predis": "^2.3", "resend/resend-php": "^0.10.0", - "symfony/cache": "^7.0", - "symfony/http-client": "^7.0", - "symfony/psr-http-message-bridge": "^7.0" + "symfony/cache": "^7.0.3", + "symfony/http-client": "^7.0.3", + "symfony/psr-http-message-bridge": "^7.0.3", + "symfony/translation": "^7.0.3" }, "suggest": { "ably/ably-php": "Required to use the Ably broadcast driver (^1.0).", - "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.235.5).", + "aws/aws-sdk-php": "Required to use the SQS queue driver, DynamoDb failed job storage, and SES mail driver (^3.322.9).", "brianium/paratest": "Required to run tests in parallel (^7.0|^8.0).", "ext-apcu": "Required to use the APC cache driver.", "ext-fileinfo": "Required to use the Filesystem class.", @@ -2867,16 +2870,16 @@ "fakerphp/faker": "Required to use the eloquent factory builder (^1.9.1).", "filp/whoops": "Required for friendly error pages in development (^2.14.3).", "laravel/tinker": "Required to use the tinker console command (^2.0).", - "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.0).", - "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.0).", - "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.3).", - "league/flysystem-read-only": "Required to use read-only disks (^3.3)", - "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.0).", + "league/flysystem-aws-s3-v3": "Required to use the Flysystem S3 driver (^3.25.1).", + "league/flysystem-ftp": "Required to use the Flysystem FTP driver (^3.25.1).", + "league/flysystem-path-prefixing": "Required to use the scoped driver (^3.25.1).", + "league/flysystem-read-only": "Required to use read-only disks (^3.25.1)", + "league/flysystem-sftp-v3": "Required to use the Flysystem SFTP driver (^3.25.1).", "mockery/mockery": "Required to use mocking (^1.6).", - "nyholm/psr7": "Required to use PSR-7 bridging features (^1.2).", "pda/pheanstalk": "Required to use the beanstalk queue driver (^5.0).", + "php-http/discovery": "Required to use PSR-7 bridging features (^1.15).", "phpunit/phpunit": "Required to use assertions and run tests (^10.5|^11.0).", - "predis/predis": "Required to use the predis connector (^2.0.2).", + "predis/predis": "Required to use the predis connector (^2.3).", "psr/http-message": "Required to allow Storage::put to accept a StreamInterface (^1.0).", "pusher/pusher-php-server": "Required to use the Pusher broadcast driver (^6.0|^7.0).", "resend/resend-php": "Required to enable support for the Resend mail transport (^0.10.0).", @@ -2895,6 +2898,7 @@ }, "autoload": { "files": [ + "src/Illuminate/Collections/functions.php", "src/Illuminate/Collections/helpers.php", "src/Illuminate/Events/functions.php", "src/Illuminate/Filesystem/functions.php", @@ -2932,20 +2936,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-11-12T15:36:15+00:00" + "time": "2024-12-10T16:09:29+00:00" }, { "name": "laravel/horizon", - "version": "v5.29.3", + "version": "v5.30.0", "source": { "type": "git", "url": "https://github.com/laravel/horizon.git", - "reference": "a48d242759704e598242074daf0060bbeb6ed46d" + "reference": "37d1f29daa7500fcd170d5c45b98b592fcaab95a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/horizon/zipball/a48d242759704e598242074daf0060bbeb6ed46d", - "reference": "a48d242759704e598242074daf0060bbeb6ed46d", + "url": "https://api.github.com/repos/laravel/horizon/zipball/37d1f29daa7500fcd170d5c45b98b592fcaab95a", + "reference": "37d1f29daa7500fcd170d5c45b98b592fcaab95a", "shasum": "" }, "require": { @@ -2976,16 +2980,16 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - }, "laravel": { - "providers": [ - "Laravel\\Horizon\\HorizonServiceProvider" - ], "aliases": { "Horizon": "Laravel\\Horizon\\Horizon" - } + }, + "providers": [ + "Laravel\\Horizon\\HorizonServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "5.x-dev" } }, "autoload": { @@ -3010,9 +3014,9 @@ ], "support": { "issues": "https://github.com/laravel/horizon/issues", - "source": "https://github.com/laravel/horizon/tree/v5.29.3" + "source": "https://github.com/laravel/horizon/tree/v5.30.0" }, - "time": "2024-11-07T21:51:45+00:00" + "time": "2024-12-06T18:58:00+00:00" }, { "name": "laravel/pail", @@ -3153,16 +3157,16 @@ }, { "name": "laravel/sanctum", - "version": "v4.0.3", + "version": "v4.0.6", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "54aea9d13743ae8a6cdd3c28dbef128a17adecab" + "reference": "9e069e36d90b1e1f41886efa0fe9800a6b354694" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/54aea9d13743ae8a6cdd3c28dbef128a17adecab", - "reference": "54aea9d13743ae8a6cdd3c28dbef128a17adecab", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/9e069e36d90b1e1f41886efa0fe9800a6b354694", + "reference": "9e069e36d90b1e1f41886efa0fe9800a6b354694", "shasum": "" }, "require": { @@ -3213,36 +3217,36 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2024-09-27T14:55:41+00:00" + "time": "2024-11-26T21:18:33+00:00" }, { "name": "laravel/serializable-closure", - "version": "v1.3.6", + "version": "v2.0.0", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "f865a58ea3a0107c336b7045104c75243fa59d96" + "reference": "0d8d3d8086984996df86596a86dea60398093a81" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f865a58ea3a0107c336b7045104c75243fa59d96", - "reference": "f865a58ea3a0107c336b7045104c75243fa59d96", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/0d8d3d8086984996df86596a86dea60398093a81", + "reference": "0d8d3d8086984996df86596a86dea60398093a81", "shasum": "" }, "require": { - "php": "^7.3|^8.0" + "php": "^8.1" }, "require-dev": { - "illuminate/support": "^8.0|^9.0|^10.0|^11.0", - "nesbot/carbon": "^2.61|^3.0", - "pestphp/pest": "^1.21.3", - "phpstan/phpstan": "^1.8.2", - "symfony/var-dumper": "^5.4.11|^6.2.0|^7.0.0" + "illuminate/support": "^10.0|^11.0", + "nesbot/carbon": "^2.67|^3.0", + "pestphp/pest": "^2.36", + "phpstan/phpstan": "^2.0", + "symfony/var-dumper": "^6.2.0|^7.0.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.x-dev" + "dev-master": "2.x-dev" } }, "autoload": { @@ -3274,7 +3278,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2024-11-11T17:06:04+00:00" + "time": "2024-11-19T01:38:44+00:00" }, { "name": "laravel/socialite", @@ -3309,16 +3313,16 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - }, "laravel": { - "providers": [ - "Laravel\\Socialite\\SocialiteServiceProvider" - ], "aliases": { "Socialite": "Laravel\\Socialite\\Facades\\Socialite" - } + }, + "providers": [ + "Laravel\\Socialite\\SocialiteServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "5.x-dev" } }, "autoload": { @@ -3416,16 +3420,16 @@ }, { "name": "laravel/ui", - "version": "v4.5.2", + "version": "v4.6.0", "source": { "type": "git", "url": "https://github.com/laravel/ui.git", - "reference": "c75396f63268c95b053c8e4814eb70e0875e9628" + "reference": "a34609b15ae0c0512a0cf47a21695a2729cb7f93" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/ui/zipball/c75396f63268c95b053c8e4814eb70e0875e9628", - "reference": "c75396f63268c95b053c8e4814eb70e0875e9628", + "url": "https://api.github.com/repos/laravel/ui/zipball/a34609b15ae0c0512a0cf47a21695a2729cb7f93", + "reference": "a34609b15ae0c0512a0cf47a21695a2729cb7f93", "shasum": "" }, "require": { @@ -3473,9 +3477,9 @@ "ui" ], "support": { - "source": "https://github.com/laravel/ui/tree/v4.5.2" + "source": "https://github.com/laravel/ui/tree/v4.6.0" }, - "time": "2024-05-08T18:07:10+00:00" + "time": "2024-11-21T15:06:41+00:00" }, { "name": "lcobucci/jwt", @@ -3552,16 +3556,16 @@ }, { "name": "league/commonmark", - "version": "2.5.3", + "version": "2.6.0", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "b650144166dfa7703e62a22e493b853b58d874b0" + "reference": "d150f911e0079e90ae3c106734c93137c184f932" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/b650144166dfa7703e62a22e493b853b58d874b0", - "reference": "b650144166dfa7703e62a22e493b853b58d874b0", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/d150f911e0079e90ae3c106734c93137c184f932", + "reference": "d150f911e0079e90ae3c106734c93137c184f932", "shasum": "" }, "require": { @@ -3586,8 +3590,9 @@ "phpstan/phpstan": "^1.8.2", "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3 | ^6.0 || ^7.0", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 || ^7.0", + "symfony/finder": "^5.3 | ^6.0 | ^7.0", + "symfony/process": "^5.4 | ^6.0 | ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 | ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", "vimeo/psalm": "^4.24.0 || ^5.0.0" }, @@ -3597,7 +3602,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "2.6-dev" + "dev-main": "2.7-dev" } }, "autoload": { @@ -3654,7 +3659,7 @@ "type": "tidelift" } ], - "time": "2024-08-16T11:46:16+00:00" + "time": "2024-12-07T15:34:16+00:00" }, { "name": "league/config", @@ -4032,16 +4037,16 @@ }, { "name": "league/oauth1-client", - "version": "v1.10.1", + "version": "v1.11.0", "source": { "type": "git", "url": "https://github.com/thephpleague/oauth1-client.git", - "reference": "d6365b901b5c287dd41f143033315e2f777e1167" + "reference": "f9c94b088837eb1aae1ad7c4f23eb65cc6993055" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/d6365b901b5c287dd41f143033315e2f777e1167", - "reference": "d6365b901b5c287dd41f143033315e2f777e1167", + "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/f9c94b088837eb1aae1ad7c4f23eb65cc6993055", + "reference": "f9c94b088837eb1aae1ad7c4f23eb65cc6993055", "shasum": "" }, "require": { @@ -4102,26 +4107,26 @@ ], "support": { "issues": "https://github.com/thephpleague/oauth1-client/issues", - "source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.1" + "source": "https://github.com/thephpleague/oauth1-client/tree/v1.11.0" }, - "time": "2022-04-15T14:02:14+00:00" + "time": "2024-12-10T19:59:05+00:00" }, { "name": "league/uri", - "version": "7.4.1", + "version": "7.5.1", "source": { "type": "git", "url": "https://github.com/thephpleague/uri.git", - "reference": "bedb6e55eff0c933668addaa7efa1e1f2c417cc4" + "reference": "81fb5145d2644324614cc532b28efd0215bda430" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri/zipball/bedb6e55eff0c933668addaa7efa1e1f2c417cc4", - "reference": "bedb6e55eff0c933668addaa7efa1e1f2c417cc4", + "url": "https://api.github.com/repos/thephpleague/uri/zipball/81fb5145d2644324614cc532b28efd0215bda430", + "reference": "81fb5145d2644324614cc532b28efd0215bda430", "shasum": "" }, "require": { - "league/uri-interfaces": "^7.3", + "league/uri-interfaces": "^7.5", "php": "^8.1" }, "conflict": { @@ -4186,7 +4191,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri/tree/7.4.1" + "source": "https://github.com/thephpleague/uri/tree/7.5.1" }, "funding": [ { @@ -4194,20 +4199,20 @@ "type": "github" } ], - "time": "2024-03-23T07:42:40+00:00" + "time": "2024-12-08T08:40:02+00:00" }, { "name": "league/uri-interfaces", - "version": "7.4.1", + "version": "7.5.0", "source": { "type": "git", "url": "https://github.com/thephpleague/uri-interfaces.git", - "reference": "8d43ef5c841032c87e2de015972c06f3865ef718" + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/8d43ef5c841032c87e2de015972c06f3865ef718", - "reference": "8d43ef5c841032c87e2de015972c06f3865ef718", + "url": "https://api.github.com/repos/thephpleague/uri-interfaces/zipball/08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", + "reference": "08cfc6c4f3d811584fb09c37e2849e6a7f9b0742", "shasum": "" }, "require": { @@ -4270,7 +4275,7 @@ "docs": "https://uri.thephpleague.com", "forum": "https://thephpleague.slack.com", "issues": "https://github.com/thephpleague/uri-src/issues", - "source": "https://github.com/thephpleague/uri-interfaces/tree/7.4.1" + "source": "https://github.com/thephpleague/uri-interfaces/tree/7.5.0" }, "funding": [ { @@ -4278,20 +4283,20 @@ "type": "github" } ], - "time": "2024-03-23T07:42:40+00:00" + "time": "2024-12-08T08:18:47+00:00" }, { "name": "livewire/livewire", - "version": "v3.5.12", + "version": "v3.5.17", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d" + "reference": "7bbf80d93db9b866776bf957ca6229364bca8d87" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d", - "reference": "3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d", + "url": "https://api.github.com/repos/livewire/livewire/zipball/7bbf80d93db9b866776bf957ca6229364bca8d87", + "reference": "7bbf80d93db9b866776bf957ca6229364bca8d87", "shasum": "" }, "require": { @@ -4317,12 +4322,12 @@ "type": "library", "extra": { "laravel": { - "providers": [ - "Livewire\\LivewireServiceProvider" - ], "aliases": { "Livewire": "Livewire\\Livewire" - } + }, + "providers": [ + "Livewire\\LivewireServiceProvider" + ] } }, "autoload": { @@ -4346,7 +4351,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.5.12" + "source": "https://github.com/livewire/livewire/tree/v3.5.17" }, "funding": [ { @@ -4354,7 +4359,7 @@ "type": "github" } ], - "time": "2024-10-15T19:35:06+00:00" + "time": "2024-12-06T13:41:21+00:00" }, { "name": "log1x/laravel-webfonts", @@ -4445,12 +4450,12 @@ "type": "library", "extra": { "laravel": { - "providers": [ - "Lorisleiva\\Actions\\ActionServiceProvider" - ], "aliases": { "Action": "Lorisleiva\\Actions\\Facades\\Actions" - } + }, + "providers": [ + "Lorisleiva\\Actions\\ActionServiceProvider" + ] } }, "autoload": { @@ -4568,16 +4573,16 @@ }, { "name": "monolog/monolog", - "version": "3.8.0", + "version": "3.8.1", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67" + "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/32e515fdc02cdafbe4593e30a9350d486b125b67", - "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/aef6ee73a77a66e404dd6540934a9ef1b3c855b4", + "reference": "aef6ee73a77a66e404dd6540934a9ef1b3c855b4", "shasum": "" }, "require": { @@ -4655,7 +4660,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.8.0" + "source": "https://github.com/Seldaek/monolog/tree/3.8.1" }, "funding": [ { @@ -4667,7 +4672,7 @@ "type": "tidelift" } ], - "time": "2024-11-12T13:57:08+00:00" + "time": "2024-12-05T17:15:07+00:00" }, { "name": "mtdowling/jmespath.php", @@ -5102,31 +5107,31 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.2.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "42c84e4e8090766bbd6445d06cd6e57650626ea3" + "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/42c84e4e8090766bbd6445d06cd6e57650626ea3", - "reference": "42c84e4e8090766bbd6445d06cd6e57650626ea3", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/52915afe6a1044e8b9cee1bcff836fb63acf9cda", + "reference": "52915afe6a1044e8b9cee1bcff836fb63acf9cda", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.1.5" + "symfony/console": "^7.1.8" }, "require-dev": { - "illuminate/console": "^11.28.0", - "laravel/pint": "^1.18.1", + "illuminate/console": "^11.33.2", + "laravel/pint": "^1.18.2", "mockery/mockery": "^1.6.12", "pestphp/pest": "^2.36.0", - "phpstan/phpstan": "^1.12.6", + "phpstan/phpstan": "^1.12.11", "phpstan/phpstan-strict-rules": "^1.6.1", - "symfony/var-dumper": "^7.1.5", + "symfony/var-dumper": "^7.1.8", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -5169,7 +5174,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.2.0" + "source": "https://github.com/nunomaduro/termwind/tree/v2.3.0" }, "funding": [ { @@ -5185,7 +5190,7 @@ "type": "github" } ], - "time": "2024-10-15T16:15:16+00:00" + "time": "2024-11-21T10:39:51+00:00" }, { "name": "nyholm/psr7", @@ -5473,151 +5478,23 @@ }, "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", + "version": "6.1.0", "source": { "type": "git", "url": "https://github.com/phpDocumentor/Reflection.git", - "reference": "61e2f1fe7683e9647b9ed8d9e53d08699385267d" + "reference": "bb4dea805a645553d6d989b23dad9f8041f39502" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/61e2f1fe7683e9647b9ed8d9e53d08699385267d", - "reference": "61e2f1fe7683e9647b9ed8d9e53d08699385267d", + "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/bb4dea805a645553d6d989b23dad9f8041f39502", + "reference": "bb4dea805a645553d6d989b23dad9f8041f39502", "shasum": "" }, "require": { "nikic/php-parser": "~4.18 || ^5.0", - "php": "8.1.*|8.2.*|8.3.*", + "php": "8.1.*|8.2.*|8.3.*|8.4.*", "phpdocumentor/reflection-common": "^2.1", "phpdocumentor/reflection-docblock": "^5", "phpdocumentor/type-resolver": "^1.2", @@ -5664,9 +5541,9 @@ ], "support": { "issues": "https://github.com/phpDocumentor/Reflection/issues", - "source": "https://github.com/phpDocumentor/Reflection/tree/6.0.0" + "source": "https://github.com/phpDocumentor/Reflection/tree/6.1.0" }, - "time": "2024-05-23T19:28:12+00:00" + "time": "2024-11-22T15:11:54+00:00" }, { "name": "phpdocumentor/reflection-common", @@ -5723,16 +5600,16 @@ }, { "name": "phpdocumentor/reflection-docblock", - "version": "5.6.0", + "version": "5.6.1", "source": { "type": "git", "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "f3558a4c23426d12bffeaab463f8a8d8b681193c" + "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/f3558a4c23426d12bffeaab463f8a8d8b681193c", - "reference": "f3558a4c23426d12bffeaab463f8a8d8b681193c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", + "reference": "e5e784149a09bd69d9a5e3b01c5cbd2e2bd653d8", "shasum": "" }, "require": { @@ -5781,9 +5658,9 @@ "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", "support": { "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", - "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.0" + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.6.1" }, - "time": "2024-11-12T11:25:25+00:00" + "time": "2024-12-07T09:39:29+00:00" }, { "name": "phpdocumentor/type-resolver", @@ -6076,62 +5953,57 @@ "time": "2024-10-13T11:29:49+00:00" }, { - "name": "phpstan/phpstan", - "version": "1.12.10", + "name": "pimple/pimple", + "version": "v3.5.0", "source": { "type": "git", - "url": "https://github.com/phpstan/phpstan.git", - "reference": "fc463b5d0fe906dcf19689be692c65c50406a071" + "url": "https://github.com/silexphp/Pimple.git", + "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fc463b5d0fe906dcf19689be692c65c50406a071", - "reference": "fc463b5d0fe906dcf19689be692c65c50406a071", + "url": "https://api.github.com/repos/silexphp/Pimple/zipball/a94b3a4db7fb774b3d78dad2315ddc07629e1bed", + "reference": "a94b3a4db7fb774b3d78dad2315ddc07629e1bed", "shasum": "" }, "require": { - "php": "^7.2|^8.0" + "php": ">=7.2.5", + "psr/container": "^1.1 || ^2.0" }, - "conflict": { - "phpstan/phpstan-shim": "*" + "require-dev": { + "symfony/phpunit-bridge": "^5.4@dev" }, - "bin": [ - "phpstan", - "phpstan.phar" - ], "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.4.x-dev" + } + }, "autoload": { - "files": [ - "bootstrap.php" - ] + "psr-0": { + "Pimple": "src/" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], - "description": "PHPStan - PHP Static Analysis Tool", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + } + ], + "description": "Pimple, a simple Dependency Injection Container", + "homepage": "https://pimple.symfony.com", "keywords": [ - "dev", - "static analysis" + "container", + "dependency injection" ], "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" + "source": "https://github.com/silexphp/Pimple/tree/v3.5.0" }, - "funding": [ - { - "url": "https://github.com/ondrejmirtes", - "type": "github" - }, - { - "url": "https://github.com/phpstan", - "type": "github" - } - ], - "time": "2024-11-11T15:37:09+00:00" + "time": "2021-10-28T11:13:42+00:00" }, { "name": "pion/laravel-chunk-upload", @@ -6201,23 +6073,23 @@ }, { "name": "poliander/cron", - "version": "3.1.0", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/poliander/cron.git", - "reference": "9e037c06aab233787999dfba38f1a12d100510c1" + "reference": "213c477b3d9d6fcf8f0944298f481c1649a92b3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/poliander/cron/zipball/9e037c06aab233787999dfba38f1a12d100510c1", - "reference": "9e037c06aab233787999dfba38f1a12d100510c1", + "url": "https://api.github.com/repos/poliander/cron/zipball/213c477b3d9d6fcf8f0944298f481c1649a92b3b", + "reference": "213c477b3d9d6fcf8f0944298f481c1649a92b3b", "shasum": "" }, "require": { - "php": "8.1.* || 8.2.* || 8.3.*" + "php": "8.2.* || 8.3.* || 8.4.*" }, "require-dev": { - "phpunit/phpunit": "~10.0" + "phpunit/phpunit": "~11.0" }, "type": "library", "autoload": { @@ -6239,9 +6111,9 @@ "homepage": "https://github.com/poliander/cron", "support": { "issues": "https://github.com/poliander/cron/issues", - "source": "https://github.com/poliander/cron/tree/3.1.0" + "source": "https://github.com/poliander/cron/tree/3.2.0" }, - "time": "2023-11-23T21:56:03+00:00" + "time": "2024-11-22T08:35:47+00:00" }, { "name": "pragmarx/google2fa", @@ -6758,16 +6630,16 @@ }, { "name": "psy/psysh", - "version": "v0.12.4", + "version": "v0.12.7", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "2fd717afa05341b4f8152547f142cd2f130f6818" + "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/2fd717afa05341b4f8152547f142cd2f130f6818", - "reference": "2fd717afa05341b4f8152547f142cd2f130f6818", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", + "reference": "d73fa3c74918ef4522bb8a3bf9cab39161c4b57c", "shasum": "" }, "require": { @@ -6794,12 +6666,12 @@ ], "type": "library", "extra": { - "branch-alias": { - "dev-main": "0.12.x-dev" - }, "bamarni-bin": { "bin-links": false, "forward-command": false + }, + "branch-alias": { + "dev-main": "0.12.x-dev" } }, "autoload": { @@ -6831,9 +6703,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.12.4" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.7" }, - "time": "2024-06-10T01:18:23+00:00" + "time": "2024-12-10T01:58:33+00:00" }, { "name": "purplepixie/phpdns", @@ -7169,65 +7041,6 @@ ], "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", @@ -7517,16 +7330,16 @@ }, { "name": "sentry/sentry-laravel", - "version": "4.10.0", + "version": "4.10.1", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "cbdd224cc5a224528bf6b19507ad76187b3bccfa" + "reference": "1c007fb111ff00f02efba2aca022310dae412c3a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/cbdd224cc5a224528bf6b19507ad76187b3bccfa", - "reference": "cbdd224cc5a224528bf6b19507ad76187b3bccfa", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/1c007fb111ff00f02efba2aca022310dae412c3a", + "reference": "1c007fb111ff00f02efba2aca022310dae412c3a", "shasum": "" }, "require": { @@ -7550,13 +7363,13 @@ "type": "library", "extra": { "laravel": { + "aliases": { + "Sentry": "Sentry\\Laravel\\Facade" + }, "providers": [ "Sentry\\Laravel\\ServiceProvider", "Sentry\\Laravel\\Tracing\\ServiceProvider" - ], - "aliases": { - "Sentry": "Sentry\\Laravel\\Facade" - } + ] } }, "autoload": { @@ -7590,7 +7403,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-laravel/issues", - "source": "https://github.com/getsentry/sentry-laravel/tree/4.10.0" + "source": "https://github.com/getsentry/sentry-laravel/tree/4.10.1" }, "funding": [ { @@ -7602,7 +7415,57 @@ "type": "custom" } ], - "time": "2024-11-07T08:05:24+00:00" + "time": "2024-11-24T11:02:20+00:00" + }, + { + "name": "socialiteproviders/authentik", + "version": "5.2.0", + "source": { + "type": "git", + "url": "https://github.com/SocialiteProviders/Authentik.git", + "reference": "4cf129cf04728a38e0531c54454464b162f0fa66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/SocialiteProviders/Authentik/zipball/4cf129cf04728a38e0531c54454464b162f0fa66", + "reference": "4cf129cf04728a38e0531c54454464b162f0fa66", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": "^8.0", + "socialiteproviders/manager": "^4.4" + }, + "type": "library", + "autoload": { + "psr-4": { + "SocialiteProviders\\Authentik\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "rf152", + "email": "git@rf152.co.uk" + } + ], + "description": "Authentik OAuth2 Provider for Laravel Socialite", + "keywords": [ + "authentik", + "laravel", + "oauth", + "provider", + "socialite" + ], + "support": { + "docs": "https://socialiteproviders.com/authentik", + "issues": "https://github.com/socialiteproviders/providers/issues", + "source": "https://github.com/socialiteproviders/providers" + }, + "time": "2023-11-07T22:21:16+00:00" }, { "name": "socialiteproviders/manager", @@ -7731,27 +7594,27 @@ }, { "name": "spatie/backtrace", - "version": "1.6.2", + "version": "1.7.1", "source": { "type": "git", "url": "https://github.com/spatie/backtrace.git", - "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9" + "reference": "0f2477c520e3729de58e061b8192f161c99f770b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/backtrace/zipball/1a9a145b044677ae3424693f7b06479fc8c137a9", - "reference": "1a9a145b044677ae3424693f7b06479fc8c137a9", + "url": "https://api.github.com/repos/spatie/backtrace/zipball/0f2477c520e3729de58e061b8192f161c99f770b", + "reference": "0f2477c520e3729de58e061b8192f161c99f770b", "shasum": "" }, "require": { - "php": "^7.3|^8.0" + "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" + "laravel/serializable-closure": "^1.3 || ^2.0", + "phpunit/phpunit": "^9.3 || ^11.4.3", + "spatie/phpunit-snapshot-assertions": "^4.2 || ^5.1.6", + "symfony/var-dumper": "^5.1 || ^6.0 || ^7.0" }, "type": "library", "autoload": { @@ -7778,7 +7641,7 @@ "spatie" ], "support": { - "source": "https://github.com/spatie/backtrace/tree/1.6.2" + "source": "https://github.com/spatie/backtrace/tree/1.7.1" }, "funding": [ { @@ -7790,20 +7653,20 @@ "type": "other" } ], - "time": "2024-07-22T08:21:24+00:00" + "time": "2024-12-02T13:28:15+00:00" }, { "name": "spatie/laravel-activitylog", - "version": "4.9.0", + "version": "4.9.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-activitylog.git", - "reference": "e0fc28178515a5396f48e107ed697719189bbe02" + "reference": "9abddaa9f2681d97943748c7fa04161cf4642e8c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/e0fc28178515a5396f48e107ed697719189bbe02", - "reference": "e0fc28178515a5396f48e107ed697719189bbe02", + "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/9abddaa9f2681d97943748c7fa04161cf4642e8c", + "reference": "9abddaa9f2681d97943748c7fa04161cf4642e8c", "shasum": "" }, "require": { @@ -7869,7 +7732,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-activitylog/issues", - "source": "https://github.com/spatie/laravel-activitylog/tree/4.9.0" + "source": "https://github.com/spatie/laravel-activitylog/tree/4.9.1" }, "funding": [ { @@ -7881,7 +7744,7 @@ "type": "github" } ], - "time": "2024-10-18T13:38:47+00:00" + "time": "2024-11-18T11:31:57+00:00" }, { "name": "spatie/laravel-data", @@ -7969,16 +7832,16 @@ }, { "name": "spatie/laravel-package-tools", - "version": "1.16.5", + "version": "1.17.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-package-tools.git", - "reference": "c7413972cf22ffdff97b68499c22baa04eddb6a2" + "reference": "9ab30fd24f677e5aa370ea4cf6b41c517d16cf85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/c7413972cf22ffdff97b68499c22baa04eddb6a2", - "reference": "c7413972cf22ffdff97b68499c22baa04eddb6a2", + "url": "https://api.github.com/repos/spatie/laravel-package-tools/zipball/9ab30fd24f677e5aa370ea4cf6b41c517d16cf85", + "reference": "9ab30fd24f677e5aa370ea4cf6b41c517d16cf85", "shasum": "" }, "require": { @@ -7987,10 +7850,10 @@ }, "require-dev": { "mockery/mockery": "^1.5", - "orchestra/testbench": "^7.7|^8.0", - "pestphp/pest": "^1.22", - "phpunit/phpunit": "^9.5.24", - "spatie/pest-plugin-test-time": "^1.1" + "orchestra/testbench": "^7.7|^8.0|^9.0", + "pestphp/pest": "^1.22|^2", + "phpunit/phpunit": "^9.5.24|^10.5", + "spatie/pest-plugin-test-time": "^1.1|^2.2" }, "type": "library", "autoload": { @@ -8017,7 +7880,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-package-tools/issues", - "source": "https://github.com/spatie/laravel-package-tools/tree/1.16.5" + "source": "https://github.com/spatie/laravel-package-tools/tree/1.17.0" }, "funding": [ { @@ -8025,54 +7888,55 @@ "type": "github" } ], - "time": "2024-08-27T18:56:10+00:00" + "time": "2024-12-09T16:29:14+00:00" }, { "name": "spatie/laravel-ray", - "version": "1.37.1", + "version": "1.39.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ray.git", - "reference": "c2bedfd1172648df2c80aaceb2541d70f1d9a5b9" + "reference": "31b601f98590606d20e76b5dd68578dc1642cd2c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/c2bedfd1172648df2c80aaceb2541d70f1d9a5b9", - "reference": "c2bedfd1172648df2c80aaceb2541d70f1d9a5b9", + "url": "https://api.github.com/repos/spatie/laravel-ray/zipball/31b601f98590606d20e76b5dd68578dc1642cd2c", + "reference": "31b601f98590606d20e76b5dd68578dc1642cd2c", "shasum": "" }, "require": { + "composer-runtime-api": "^2.2", "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", + "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", "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" + "spatie/ray": "^1.41.3", + "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" + "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 || ^2.0.2", + "phpunit/phpunit": "^9.3 || ^10.1", + "rector/rector": "dev-main", + "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" ] + }, + "branch-alias": { + "dev-main": "1.x-dev" } }, "autoload": { @@ -8100,7 +7964,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-ray/issues", - "source": "https://github.com/spatie/laravel-ray/tree/1.37.1" + "source": "https://github.com/spatie/laravel-ray/tree/1.39.0" }, "funding": [ { @@ -8112,7 +7976,7 @@ "type": "other" } ], - "time": "2024-07-12T12:35:17+00:00" + "time": "2024-12-11T09:34:41+00:00" }, { "name": "spatie/laravel-schemaless-attributes", @@ -8322,35 +8186,35 @@ }, { "name": "spatie/ray", - "version": "1.41.2", + "version": "1.41.4", "source": { "type": "git", "url": "https://github.com/spatie/ray.git", - "reference": "c44f8cfbf82c69909b505de61d8d3f2d324e93fc" + "reference": "c5dbda0548c1881b30549ccc0b6d485f7471aaa5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ray/zipball/c44f8cfbf82c69909b505de61d8d3f2d324e93fc", - "reference": "c44f8cfbf82c69909b505de61d8d3f2d324e93fc", + "url": "https://api.github.com/repos/spatie/ray/zipball/c5dbda0548c1881b30549ccc0b6d485f7471aaa5", + "reference": "c5dbda0548c1881b30549ccc0b6d485f7471aaa5", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", - "php": "^7.3|^8.0", - "ramsey/uuid": "^3.0|^4.1", + "php": "^7.4 || ^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" + "spatie/macroable": "^1.0 || ^2.0", + "symfony/stopwatch": "^4.2 || ^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", + "illuminate/support": "^7.20 || ^8.18 || ^9.0 || ^10.0 || ^11.0", "nesbot/carbon": "^2.63", "pestphp/pest": "^1.22", - "phpstan/phpstan": "^1.10", + "phpstan/phpstan": "^1.10.57 || ^2.0.2", "phpunit/phpunit": "^9.5", - "rector/rector": "^0.19.2", + "rector/rector": "dev-main", "spatie/phpunit-snapshot-assertions": "^4.2", "spatie/test-time": "^1.2" }, @@ -8391,7 +8255,7 @@ ], "support": { "issues": "https://github.com/spatie/ray/issues", - "source": "https://github.com/spatie/ray/tree/1.41.2" + "source": "https://github.com/spatie/ray/tree/1.41.4" }, "funding": [ { @@ -8403,7 +8267,7 @@ "type": "other" } ], - "time": "2024-04-24T14:21:46+00:00" + "time": "2024-12-09T11:32:15+00:00" }, { "name": "spatie/url", @@ -8469,16 +8333,16 @@ }, { "name": "stripe/stripe-php", - "version": "v16.2.0", + "version": "v16.3.0", "source": { "type": "git", "url": "https://github.com/stripe/stripe-php.git", - "reference": "813ae4961755af28a13bda451689f7a6ed6498cb" + "reference": "48af6bc64ca8157b3fdce100e856069963bac466" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stripe/stripe-php/zipball/813ae4961755af28a13bda451689f7a6ed6498cb", - "reference": "813ae4961755af28a13bda451689f7a6ed6498cb", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/48af6bc64ca8157b3fdce100e856069963bac466", + "reference": "48af6bc64ca8157b3fdce100e856069963bac466", "shasum": "" }, "require": { @@ -8522,22 +8386,22 @@ ], "support": { "issues": "https://github.com/stripe/stripe-php/issues", - "source": "https://github.com/stripe/stripe-php/tree/v16.2.0" + "source": "https://github.com/stripe/stripe-php/tree/v16.3.0" }, - "time": "2024-10-29T21:15:53+00:00" + "time": "2024-11-20T23:30:16+00:00" }, { "name": "symfony/clock", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "97bebc53548684c17ed696bc8af016880f0f098d" + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/97bebc53548684c17ed696bc8af016880f0f098d", - "reference": "97bebc53548684c17ed696bc8af016880f0f098d", + "url": "https://api.github.com/repos/symfony/clock/zipball/b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", + "reference": "b81435fbd6648ea425d1ee96a2d8e68f4ceacd24", "shasum": "" }, "require": { @@ -8582,7 +8446,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.1.6" + "source": "https://github.com/symfony/clock/tree/v7.2.0" }, "funding": [ { @@ -8598,20 +8462,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/console", - "version": "v7.1.8", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5" + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/ff04e5b5ba043d2badfb308197b9e6b42883fcd5", - "reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5", + "url": "https://api.github.com/repos/symfony/console/zipball/fefcc18c0f5d0efe3ab3152f15857298868dc2c3", + "reference": "fefcc18c0f5d0efe3ab3152f15857298868dc2c3", "shasum": "" }, "require": { @@ -8675,7 +8539,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.8" + "source": "https://github.com/symfony/console/tree/v7.2.1" }, "funding": [ { @@ -8691,20 +8555,20 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:23:19+00:00" + "time": "2024-12-11T03:49:26+00:00" }, { "name": "symfony/css-selector", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "4aa4f6b3d6749c14d3aa815eef8226632e7bbc66" + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/4aa4f6b3d6749c14d3aa815eef8226632e7bbc66", - "reference": "4aa4f6b3d6749c14d3aa815eef8226632e7bbc66", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/601a5ce9aaad7bf10797e3663faefce9e26c24e2", + "reference": "601a5ce9aaad7bf10797e3663faefce9e26c24e2", "shasum": "" }, "require": { @@ -8740,7 +8604,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.1.6" + "source": "https://github.com/symfony/css-selector/tree/v7.2.0" }, "funding": [ { @@ -8756,20 +8620,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/deprecation-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1" + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", - "reference": "0e0d29ce1f20deffb4ab1b016a7257c4f1e789a1", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", + "reference": "74c71c939a79f7d5bf3c1ce9f5ea37ba0114c6f6", "shasum": "" }, "require": { @@ -8807,7 +8671,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.5.1" }, "funding": [ { @@ -8823,20 +8687,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/error-handler", - "version": "v7.1.7", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "010e44661f4c6babaf8c4862fe68c24a53903342" + "reference": "6150b89186573046167796fa5f3f76601d5145f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/010e44661f4c6babaf8c4862fe68c24a53903342", - "reference": "010e44661f4c6babaf8c4862fe68c24a53903342", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/6150b89186573046167796fa5f3f76601d5145f8", + "reference": "6150b89186573046167796fa5f3f76601d5145f8", "shasum": "" }, "require": { @@ -8882,7 +8746,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.1.7" + "source": "https://github.com/symfony/error-handler/tree/v7.2.1" }, "funding": [ { @@ -8898,20 +8762,20 @@ "type": "tidelift" } ], - "time": "2024-11-05T15:34:55+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "87254c78dd50721cfd015b62277a8281c5589702" + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87254c78dd50721cfd015b62277a8281c5589702", - "reference": "87254c78dd50721cfd015b62277a8281c5589702", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/910c5db85a5356d0fea57680defec4e99eb9c8c1", + "reference": "910c5db85a5356d0fea57680defec4e99eb9c8c1", "shasum": "" }, "require": { @@ -8962,7 +8826,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.6" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.2.0" }, "funding": [ { @@ -8978,20 +8842,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/event-dispatcher-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50" + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/8f93aec25d41b72493c6ddff14e916177c9efc50", - "reference": "8f93aec25d41b72493c6ddff14e916177c9efc50", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/7642f5e970b672283b7823222ae8ef8bbc160b9f", + "reference": "7642f5e970b672283b7823222ae8ef8bbc160b9f", "shasum": "" }, "require": { @@ -9038,7 +8902,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v3.5.1" }, "funding": [ { @@ -9054,20 +8918,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/finder", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8" + "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/2cb89664897be33f78c65d3d2845954c8d7a43b8", - "reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8", + "url": "https://api.github.com/repos/symfony/finder/zipball/6de263e5868b9a137602dd1e33e4d48bfae99c49", + "reference": "6de263e5868b9a137602dd1e33e4d48bfae99c49", "shasum": "" }, "require": { @@ -9102,7 +8966,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.1.6" + "source": "https://github.com/symfony/finder/tree/v7.2.0" }, "funding": [ { @@ -9118,24 +8982,25 @@ "type": "tidelift" } ], - "time": "2024-10-01T08:31:23+00:00" + "time": "2024-10-23T06:56:12+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.1.8", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "f4419ec69ccfc3f725a4de7c20e4e57626d10112" + "reference": "e88a66c3997859532bc2ddd6dd8f35aba2711744" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f4419ec69ccfc3f725a4de7c20e4e57626d10112", - "reference": "f4419ec69ccfc3f725a4de7c20e4e57626d10112", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e88a66c3997859532bc2ddd6dd8f35aba2711744", + "reference": "e88a66c3997859532bc2ddd6dd8f35aba2711744", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-mbstring": "~1.1", "symfony/polyfill-php83": "^1.27" }, @@ -9179,7 +9044,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v7.1.8" + "source": "https://github.com/symfony/http-foundation/tree/v7.2.0" }, "funding": [ { @@ -9195,20 +9060,20 @@ "type": "tidelift" } ], - "time": "2024-11-09T09:16:45+00:00" + "time": "2024-11-13T18:58:46+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.1.8", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "33fef24e3dc79d6d30bf4936531f2f4bd2ca189e" + "reference": "d8ae58eecae44c8e66833e76cc50a4ad3c002d97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/33fef24e3dc79d6d30bf4936531f2f4bd2ca189e", - "reference": "33fef24e3dc79d6d30bf4936531f2f4bd2ca189e", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/d8ae58eecae44c8e66833e76cc50a4ad3c002d97", + "reference": "d8ae58eecae44c8e66833e76cc50a4ad3c002d97", "shasum": "" }, "require": { @@ -9237,7 +9102,7 @@ "symfony/twig-bridge": "<6.4", "symfony/validator": "<6.4", "symfony/var-dumper": "<6.4", - "twig/twig": "<3.0.4" + "twig/twig": "<3.12" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" @@ -9265,7 +9130,7 @@ "symfony/validator": "^6.4|^7.0", "symfony/var-dumper": "^6.4|^7.0", "symfony/var-exporter": "^6.4|^7.0", - "twig/twig": "^3.0.4" + "twig/twig": "^3.12" }, "type": "library", "autoload": { @@ -9293,7 +9158,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.1.8" + "source": "https://github.com/symfony/http-kernel/tree/v7.2.1" }, "funding": [ { @@ -9309,20 +9174,20 @@ "type": "tidelift" } ], - "time": "2024-11-13T14:25:32+00:00" + "time": "2024-12-11T12:09:10+00:00" }, { "name": "symfony/mailer", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "69c9948451fb3a6a4d47dc8261d1794734e76cdd" + "reference": "e4d358702fb66e4c8a2af08e90e7271a62de39cc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/69c9948451fb3a6a4d47dc8261d1794734e76cdd", - "reference": "69c9948451fb3a6a4d47dc8261d1794734e76cdd", + "url": "https://api.github.com/repos/symfony/mailer/zipball/e4d358702fb66e4c8a2af08e90e7271a62de39cc", + "reference": "e4d358702fb66e4c8a2af08e90e7271a62de39cc", "shasum": "" }, "require": { @@ -9331,7 +9196,7 @@ "psr/event-dispatcher": "^1", "psr/log": "^1|^2|^3", "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", + "symfony/mime": "^7.2", "symfony/service-contracts": "^2.5|^3" }, "conflict": { @@ -9373,7 +9238,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.1.6" + "source": "https://github.com/symfony/mailer/tree/v7.2.0" }, "funding": [ { @@ -9389,20 +9254,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-11-25T15:21:05+00:00" }, { "name": "symfony/mime", - "version": "v7.1.6", + "version": "v7.2.1", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "caa1e521edb2650b8470918dfe51708c237f0598" + "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/caa1e521edb2650b8470918dfe51708c237f0598", - "reference": "caa1e521edb2650b8470918dfe51708c237f0598", + "url": "https://api.github.com/repos/symfony/mime/zipball/7f9617fcf15cb61be30f8b252695ed5e2bfac283", + "reference": "7f9617fcf15cb61be30f8b252695ed5e2bfac283", "shasum": "" }, "require": { @@ -9457,7 +9322,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.1.6" + "source": "https://github.com/symfony/mime/tree/v7.2.1" }, "funding": [ { @@ -9473,20 +9338,20 @@ "type": "tidelift" } ], - "time": "2024-10-25T15:11:02+00:00" + "time": "2024-12-07T08:50:44+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "85e95eeede2d41cd146146e98c9c81d9214cae85" + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/85e95eeede2d41cd146146e98c9c81d9214cae85", - "reference": "85e95eeede2d41cd146146e98c9c81d9214cae85", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/7da8fbac9dcfef75ffc212235d76b2754ce0cf50", + "reference": "7da8fbac9dcfef75ffc212235d76b2754ce0cf50", "shasum": "" }, "require": { @@ -9524,7 +9389,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.1.6" + "source": "https://github.com/symfony/options-resolver/tree/v7.2.0" }, "funding": [ { @@ -9540,7 +9405,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-11-20T11:17:29+00:00" }, { "name": "symfony/polyfill-ctype", @@ -9568,8 +9433,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9724,8 +9589,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9803,8 +9668,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9885,8 +9750,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -9969,8 +9834,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -10043,8 +9908,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -10123,8 +9988,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -10205,8 +10070,8 @@ "type": "library", "extra": { "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "url": "https://github.com/symfony/polyfill", + "name": "symfony/polyfill" } }, "autoload": { @@ -10260,16 +10125,16 @@ }, { "name": "symfony/process", - "version": "v7.1.8", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892" + "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/42783370fda6e538771f7c7a36e9fa2ee3a84892", - "reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892", + "url": "https://api.github.com/repos/symfony/process/zipball/d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", + "reference": "d34b22ba9390ec19d2dd966c40aa9e8462f27a7e", "shasum": "" }, "require": { @@ -10301,7 +10166,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.8" + "source": "https://github.com/symfony/process/tree/v7.2.0" }, "funding": [ { @@ -10317,20 +10182,20 @@ "type": "tidelift" } ], - "time": "2024-11-06T14:23:19+00:00" + "time": "2024-11-06T14:24:19+00:00" }, { "name": "symfony/psr-http-message-bridge", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "f16471bb19f6685b9ccf0a2c03c213840ae68cd6" + "reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/f16471bb19f6685b9ccf0a2c03c213840ae68cd6", - "reference": "f16471bb19f6685b9ccf0a2c03c213840ae68cd6", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/03f2f72319e7acaf2a9f6fcbe30ef17eec51594f", + "reference": "03f2f72319e7acaf2a9f6fcbe30ef17eec51594f", "shasum": "" }, "require": { @@ -10384,7 +10249,7 @@ "psr-7" ], "support": { - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.1.6" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.2.0" }, "funding": [ { @@ -10400,20 +10265,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-26T08:57:56+00:00" }, { "name": "symfony/routing", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "66a2c469f6c22d08603235c46a20007c0701ea0a" + "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/66a2c469f6c22d08603235c46a20007c0701ea0a", - "reference": "66a2c469f6c22d08603235c46a20007c0701ea0a", + "url": "https://api.github.com/repos/symfony/routing/zipball/e10a2450fa957af6c448b9b93c9010a4e4c0725e", + "reference": "e10a2450fa957af6c448b9b93c9010a4e4c0725e", "shasum": "" }, "require": { @@ -10465,7 +10330,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.1.6" + "source": "https://github.com/symfony/routing/tree/v7.2.0" }, "funding": [ { @@ -10481,20 +10346,20 @@ "type": "tidelift" } ], - "time": "2024-10-01T08:31:23+00:00" + "time": "2024-11-25T11:08:51+00:00" }, { "name": "symfony/service-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f" + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", - "reference": "bd1d9e59a81d8fa4acdcea3f617c581f7475a80f", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/e53260aabf78fb3d63f8d79d69ece59f80d5eda0", + "reference": "e53260aabf78fb3d63f8d79d69ece59f80d5eda0", "shasum": "" }, "require": { @@ -10548,7 +10413,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/service-contracts/tree/v3.5.1" }, "funding": [ { @@ -10564,20 +10429,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/stopwatch", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05" + "reference": "696f418b0d722a4225e1c3d95489d262971ca924" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/8b4a434e6e7faf6adedffb48783a5c75409a1a05", - "reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/696f418b0d722a4225e1c3d95489d262971ca924", + "reference": "696f418b0d722a4225e1c3d95489d262971ca924", "shasum": "" }, "require": { @@ -10610,7 +10475,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.1.6" + "source": "https://github.com/symfony/stopwatch/tree/v7.2.0" }, "funding": [ { @@ -10626,20 +10491,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/string", - "version": "v7.1.8", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "591ebd41565f356fcd8b090fe64dbb5878f50281" + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/591ebd41565f356fcd8b090fe64dbb5878f50281", - "reference": "591ebd41565f356fcd8b090fe64dbb5878f50281", + "url": "https://api.github.com/repos/symfony/string/zipball/446e0d146f991dde3e73f45f2c97a9faad773c82", + "reference": "446e0d146f991dde3e73f45f2c97a9faad773c82", "shasum": "" }, "require": { @@ -10697,7 +10562,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.8" + "source": "https://github.com/symfony/string/tree/v7.2.0" }, "funding": [ { @@ -10713,24 +10578,25 @@ "type": "tidelift" } ], - "time": "2024-11-13T13:31:21+00:00" + "time": "2024-11-13T13:31:26+00:00" }, { "name": "symfony/translation", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "b9f72ab14efdb6b772f85041fa12f820dee8d55f" + "reference": "dc89e16b44048ceecc879054e5b7f38326ab6cc5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/b9f72ab14efdb6b772f85041fa12f820dee8d55f", - "reference": "b9f72ab14efdb6b772f85041fa12f820dee8d55f", + "url": "https://api.github.com/repos/symfony/translation/zipball/dc89e16b44048ceecc879054e5b7f38326ab6cc5", + "reference": "dc89e16b44048ceecc879054e5b7f38326ab6cc5", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/translation-contracts": "^2.5|^3.0" }, @@ -10791,7 +10657,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.1.6" + "source": "https://github.com/symfony/translation/tree/v7.2.0" }, "funding": [ { @@ -10807,20 +10673,20 @@ "type": "tidelift" } ], - "time": "2024-09-28T12:35:13+00:00" + "time": "2024-11-12T20:47:56+00:00" }, { "name": "symfony/translation-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/translation-contracts.git", - "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a" + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", - "reference": "b9d2189887bb6b2e0367a9fc7136c5239ab9b05a", + "url": "https://api.github.com/repos/symfony/translation-contracts/zipball/4667ff3bd513750603a09c8dedbea942487fb07c", + "reference": "4667ff3bd513750603a09c8dedbea942487fb07c", "shasum": "" }, "require": { @@ -10869,7 +10735,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/translation-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/translation-contracts/tree/v3.5.1" }, "funding": [ { @@ -10885,20 +10751,20 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/uid", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "65befb3bb2d503bbffbd08c815aa38b472999917" + "reference": "2d294d0c48df244c71c105a169d0190bfb080426" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/65befb3bb2d503bbffbd08c815aa38b472999917", - "reference": "65befb3bb2d503bbffbd08c815aa38b472999917", + "url": "https://api.github.com/repos/symfony/uid/zipball/2d294d0c48df244c71c105a169d0190bfb080426", + "reference": "2d294d0c48df244c71c105a169d0190bfb080426", "shasum": "" }, "require": { @@ -10943,7 +10809,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.1.6" + "source": "https://github.com/symfony/uid/tree/v7.2.0" }, "funding": [ { @@ -10959,20 +10825,20 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-09-25T14:21:43+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.1.8", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8" + "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8", - "reference": "7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/c6a22929407dec8765d6e2b6ff85b800b245879c", + "reference": "c6a22929407dec8765d6e2b6ff85b800b245879c", "shasum": "" }, "require": { @@ -10988,7 +10854,7 @@ "symfony/http-kernel": "^6.4|^7.0", "symfony/process": "^6.4|^7.0", "symfony/uid": "^6.4|^7.0", - "twig/twig": "^3.0.4" + "twig/twig": "^3.12" }, "bin": [ "Resources/bin/var-dump-server" @@ -11026,7 +10892,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.1.8" + "source": "https://github.com/symfony/var-dumper/tree/v7.2.0" }, "funding": [ { @@ -11042,24 +10908,25 @@ "type": "tidelift" } ], - "time": "2024-11-08T15:46:42+00:00" + "time": "2024-11-08T15:48:14+00:00" }, { "name": "symfony/yaml", - "version": "v7.1.6", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "3ced3f29e4f0d6bce2170ff26719f1fe9aacc671" + "reference": "099581e99f557e9f16b43c5916c26380b54abb22" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/3ced3f29e4f0d6bce2170ff26719f1fe9aacc671", - "reference": "3ced3f29e4f0d6bce2170ff26719f1fe9aacc671", + "url": "https://api.github.com/repos/symfony/yaml/zipball/099581e99f557e9f16b43c5916c26380b54abb22", + "reference": "099581e99f557e9f16b43c5916c26380b54abb22", "shasum": "" }, "require": { "php": ">=8.2", + "symfony/deprecation-contracts": "^2.5|^3.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { @@ -11097,7 +10964,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.1.6" + "source": "https://github.com/symfony/yaml/tree/v7.2.0" }, "funding": [ { @@ -11113,7 +10980,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:20:29+00:00" + "time": "2024-10-23T06:56:12+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -11312,16 +11179,16 @@ }, { "name": "voku/portable-ascii", - "version": "2.0.1", + "version": "2.0.3", "source": { "type": "git", "url": "https://github.com/voku/portable-ascii.git", - "reference": "b56450eed252f6801410d810c8e1727224ae0743" + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b56450eed252f6801410d810c8e1727224ae0743", - "reference": "b56450eed252f6801410d810c8e1727224ae0743", + "url": "https://api.github.com/repos/voku/portable-ascii/zipball/b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", + "reference": "b1d923f88091c6bf09699efcd7c8a1b1bfd7351d", "shasum": "" }, "require": { @@ -11346,7 +11213,7 @@ "authors": [ { "name": "Lars Moelleken", - "homepage": "http://www.moelleken.org/" + "homepage": "https://www.moelleken.org/" } ], "description": "Portable ASCII library - performance optimized (ascii) string functions for php.", @@ -11358,7 +11225,7 @@ ], "support": { "issues": "https://github.com/voku/portable-ascii/issues", - "source": "https://github.com/voku/portable-ascii/tree/2.0.1" + "source": "https://github.com/voku/portable-ascii/tree/2.0.3" }, "funding": [ { @@ -11382,7 +11249,7 @@ "type": "tidelift" } ], - "time": "2022-03-08T17:03:00+00:00" + "time": "2024-11-21T01:49:47+00:00" }, { "name": "webmozart/assert", @@ -11554,31 +11421,30 @@ }, { "name": "zbateson/mail-mime-parser", - "version": "3.0.3", + "version": "2.4.1", "source": { "type": "git", "url": "https://github.com/zbateson/mail-mime-parser.git", - "reference": "e0d4423fe27850c9dd301190767dbc421acc2f19" + "reference": "ff49e02f6489b38f7cc3d1bd3971adc0f872569c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/e0d4423fe27850c9dd301190767dbc421acc2f19", - "reference": "e0d4423fe27850c9dd301190767dbc421acc2f19", + "url": "https://api.github.com/repos/zbateson/mail-mime-parser/zipball/ff49e02f6489b38f7cc3d1bd3971adc0f872569c", + "reference": "ff49e02f6489b38f7cc3d1bd3971adc0f872569c", "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" + "guzzlehttp/psr7": "^1.7.0|^2.0", + "php": ">=7.1", + "pimple/pimple": "^3.0", + "zbateson/mb-wrapper": "^1.0.1", + "zbateson/stream-decorators": "^1.0.6" }, "require-dev": { "friendsofphp/php-cs-fixer": "*", - "monolog/monolog": "^2|^3", + "mikey179/vfsstream": "^1.6.0", "phpstan/phpstan": "*", - "phpunit/phpunit": "^9.6" + "phpunit/phpunit": "<10" }, "suggest": { "ext-iconv": "For best support/performance", @@ -11626,24 +11492,24 @@ "type": "github" } ], - "time": "2024-08-10T18:44:09+00:00" + "time": "2024-04-28T00:58:54+00:00" }, { "name": "zbateson/mb-wrapper", - "version": "2.0.0", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/zbateson/mb-wrapper.git", - "reference": "9e4373a153585d12b6c621ac4a6bb143264d4619" + "reference": "09a8b77eb94af3823a9a6623dcc94f8d988da67f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/9e4373a153585d12b6c621ac4a6bb143264d4619", - "reference": "9e4373a153585d12b6c621ac4a6bb143264d4619", + "url": "https://api.github.com/repos/zbateson/mb-wrapper/zipball/09a8b77eb94af3823a9a6623dcc94f8d988da67f", + "reference": "09a8b77eb94af3823a9a6623dcc94f8d988da67f", "shasum": "" }, "require": { - "php": ">=8.0", + "php": ">=7.1", "symfony/polyfill-iconv": "^1.9", "symfony/polyfill-mbstring": "^1.9" }, @@ -11687,7 +11553,7 @@ ], "support": { "issues": "https://github.com/zbateson/mb-wrapper/issues", - "source": "https://github.com/zbateson/mb-wrapper/tree/2.0.0" + "source": "https://github.com/zbateson/mb-wrapper/tree/1.2.1" }, "funding": [ { @@ -11695,31 +11561,31 @@ "type": "github" } ], - "time": "2024-03-20T01:38:07+00:00" + "time": "2024-03-18T04:31:04+00:00" }, { "name": "zbateson/stream-decorators", - "version": "2.1.1", + "version": "1.2.1", "source": { "type": "git", "url": "https://github.com/zbateson/stream-decorators.git", - "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5" + "reference": "783b034024fda8eafa19675fb2552f8654d3a3e9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/32a2a62fb0f26313395c996ebd658d33c3f9c4e5", - "reference": "32a2a62fb0f26313395c996ebd658d33c3f9c4e5", + "url": "https://api.github.com/repos/zbateson/stream-decorators/zipball/783b034024fda8eafa19675fb2552f8654d3a3e9", + "reference": "783b034024fda8eafa19675fb2552f8654d3a3e9", "shasum": "" }, "require": { - "guzzlehttp/psr7": "^2.5", - "php": ">=8.0", - "zbateson/mb-wrapper": "^2.0" + "guzzlehttp/psr7": "^1.9 | ^2.0", + "php": ">=7.2", + "zbateson/mb-wrapper": "^1.0.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "*", "phpstan/phpstan": "*", - "phpunit/phpunit": "^9.6|^10.0" + "phpunit/phpunit": "<10.0" }, "type": "library", "autoload": { @@ -11750,7 +11616,7 @@ ], "support": { "issues": "https://github.com/zbateson/stream-decorators/issues", - "source": "https://github.com/zbateson/stream-decorators/tree/2.1.1" + "source": "https://github.com/zbateson/stream-decorators/tree/1.2.1" }, "funding": [ { @@ -11758,7 +11624,7 @@ "type": "github" } ], - "time": "2024-04-29T21:42:39+00:00" + "time": "2023-05-30T22:51:52+00:00" }, { "name": "zircote/swagger-php", @@ -11845,16 +11711,16 @@ "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.14.7", + "version": "v3.14.9", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "f484b8c9124de0b163da39958331098ffcd4a65e" + "reference": "2e805a6bd4e1aa83774316bb062703c65d0691ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f484b8c9124de0b163da39958331098ffcd4a65e", - "reference": "f484b8c9124de0b163da39958331098ffcd4a65e", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/2e805a6bd4e1aa83774316bb062703c65d0691ef", + "reference": "2e805a6bd4e1aa83774316bb062703c65d0691ef", "shasum": "" }, "require": { @@ -11873,16 +11739,16 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.14-dev" - }, "laravel": { - "providers": [ - "Barryvdh\\Debugbar\\ServiceProvider" - ], "aliases": { "Debugbar": "Barryvdh\\Debugbar\\Facades\\Debugbar" - } + }, + "providers": [ + "Barryvdh\\Debugbar\\ServiceProvider" + ] + }, + "branch-alias": { + "dev-master": "3.14-dev" } }, "autoload": { @@ -11913,7 +11779,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.7" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.9" }, "funding": [ { @@ -11925,20 +11791,20 @@ "type": "github" } ], - "time": "2024-11-14T09:12:35+00:00" + "time": "2024-11-25T14:51:20+00:00" }, { "name": "brianium/paratest", - "version": "v7.6.0", + "version": "v7.6.3", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "68ff89a8de47d086588e391a516d2a5b5fde6254" + "reference": "ae3c9f1aeda7daa374c904b35ece8f574f56d176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/68ff89a8de47d086588e391a516d2a5b5fde6254", - "reference": "68ff89a8de47d086588e391a516d2a5b5fde6254", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/ae3c9f1aeda7daa374c904b35ece8f574f56d176", + "reference": "ae3c9f1aeda7daa374c904b35ece8f574f56d176", "shasum": "" }, "require": { @@ -11947,26 +11813,26 @@ "ext-reflection": "*", "ext-simplexml": "*", "fidry/cpu-core-counter": "^1.2.0", - "jean85/pretty-package-versions": "^2.0.6", + "jean85/pretty-package-versions": "^2.1.0", "php": "~8.2.0 || ~8.3.0 || ~8.4.0", "phpunit/php-code-coverage": "^11.0.7", "phpunit/php-file-iterator": "^5.1.0", "phpunit/php-timer": "^7.0.1", - "phpunit/phpunit": "^11.4.1", + "phpunit/phpunit": "^11.5.0", "sebastian/environment": "^7.2.0", - "symfony/console": "^6.4.11 || ^7.1.5", - "symfony/process": "^6.4.8 || ^7.1.5" + "symfony/console": "^6.4.14 || ^7.2.0", + "symfony/process": "^6.4.14 || ^7.2.0" }, "require-dev": { "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", - "phpstan/phpstan": "^1.12.6", - "phpstan/phpstan-deprecation-rules": "^1.2.1", - "phpstan/phpstan-phpunit": "^1.4.0", - "phpstan/phpstan-strict-rules": "^1.6.1", - "squizlabs/php_codesniffer": "^3.10.3", - "symfony/filesystem": "^6.4.9 || ^7.1.5" + "phpstan/phpstan": "^2.0.3", + "phpstan/phpstan-deprecation-rules": "^2.0.1", + "phpstan/phpstan-phpunit": "^2.0.1", + "phpstan/phpstan-strict-rules": "^2", + "squizlabs/php_codesniffer": "^3.11.1", + "symfony/filesystem": "^6.4.13 || ^7.2.0" }, "bin": [ "bin/paratest", @@ -12006,7 +11872,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.6.0" + "source": "https://github.com/paratestphp/paratest/tree/v7.6.3" }, "funding": [ { @@ -12018,20 +11884,20 @@ "type": "paypal" } ], - "time": "2024-10-15T12:38:31+00:00" + "time": "2024-12-10T13:59:28+00:00" }, { "name": "fakerphp/faker", - "version": "v1.24.0", + "version": "v1.24.1", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "a136842a532bac9ecd8a1c723852b09915d7db50" + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/a136842a532bac9ecd8a1c723852b09915d7db50", - "reference": "a136842a532bac9ecd8a1c723852b09915d7db50", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", + "reference": "e0ee18eb1e6dc3cda3ce9fd97e5a0689a88a64b5", "shasum": "" }, "require": { @@ -12079,9 +11945,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.24.0" + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.1" }, - "time": "2024-11-07T15:11:20+00:00" + "time": "2024-11-21T13:46:39+00:00" }, { "name": "fidry/cpu-core-counter", @@ -12268,16 +12134,16 @@ }, { "name": "laravel/dusk", - "version": "v8.2.11", + "version": "v8.2.12", "source": { "type": "git", "url": "https://github.com/laravel/dusk.git", - "reference": "c667db6d8795f0ccc8f63d54a7780ce8a0cc3d3c" + "reference": "a399aa31c1c9cef793ad747403017e56df77396c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/dusk/zipball/c667db6d8795f0ccc8f63d54a7780ce8a0cc3d3c", - "reference": "c667db6d8795f0ccc8f63d54a7780ce8a0cc3d3c", + "url": "https://api.github.com/repos/laravel/dusk/zipball/a399aa31c1c9cef793ad747403017e56df77396c", + "reference": "a399aa31c1c9cef793ad747403017e56df77396c", "shasum": "" }, "require": { @@ -12287,7 +12153,7 @@ "illuminate/console": "^10.0|^11.0", "illuminate/support": "^10.0|^11.0", "php": "^8.1", - "php-webdriver/webdriver": "^1.9.0", + "php-webdriver/webdriver": "^1.15.2", "symfony/console": "^6.2|^7.0", "symfony/finder": "^6.2|^7.0", "symfony/process": "^6.2|^7.0", @@ -12334,22 +12200,22 @@ ], "support": { "issues": "https://github.com/laravel/dusk/issues", - "source": "https://github.com/laravel/dusk/tree/v8.2.11" + "source": "https://github.com/laravel/dusk/tree/v8.2.12" }, - "time": "2024-11-07T21:51:32+00:00" + "time": "2024-11-21T17:37:39+00:00" }, { "name": "laravel/pint", - "version": "v1.18.1", + "version": "v1.18.3", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9" + "reference": "cef51821608239040ab841ad6e1c6ae502ae3026" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/35c00c05ec43e6b46d295efc0f4386ceb30d50d9", - "reference": "35c00c05ec43e6b46d295efc0f4386ceb30d50d9", + "url": "https://api.github.com/repos/laravel/pint/zipball/cef51821608239040ab841ad6e1c6ae502ae3026", + "reference": "cef51821608239040ab841ad6e1c6ae502ae3026", "shasum": "" }, "require": { @@ -12360,13 +12226,13 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.64.0", - "illuminate/view": "^10.48.20", - "larastan/larastan": "^2.9.8", + "friendsofphp/php-cs-fixer": "^3.65.0", + "illuminate/view": "^10.48.24", + "larastan/larastan": "^2.9.11", "laravel-zero/framework": "^10.4.0", "mockery/mockery": "^1.6.12", - "nunomaduro/termwind": "^1.15.1", - "pestphp/pest": "^2.35.1" + "nunomaduro/termwind": "^1.17.0", + "pestphp/pest": "^2.36.0" }, "bin": [ "builds/pint" @@ -12402,20 +12268,20 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2024-09-24T17:22:50+00:00" + "time": "2024-11-26T15:34:00+00:00" }, { "name": "laravel/telescope", - "version": "v5.2.5", + "version": "v5.2.6", "source": { "type": "git", "url": "https://github.com/laravel/telescope.git", - "reference": "f68386a8d816c9e3a011b8301bfd263213bf00d4" + "reference": "7ee46fbea8e3b01108575c8edf7377abddfe8bb9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/telescope/zipball/f68386a8d816c9e3a011b8301bfd263213bf00d4", - "reference": "f68386a8d816c9e3a011b8301bfd263213bf00d4", + "url": "https://api.github.com/repos/laravel/telescope/zipball/7ee46fbea8e3b01108575c8edf7377abddfe8bb9", + "reference": "7ee46fbea8e3b01108575c8edf7377abddfe8bb9", "shasum": "" }, "require": { @@ -12469,22 +12335,22 @@ ], "support": { "issues": "https://github.com/laravel/telescope/issues", - "source": "https://github.com/laravel/telescope/tree/v5.2.5" + "source": "https://github.com/laravel/telescope/tree/v5.2.6" }, - "time": "2024-10-31T17:06:07+00:00" + "time": "2024-11-25T20:34:58+00:00" }, { "name": "maximebf/debugbar", - "version": "v1.23.3", + "version": "v1.23.4", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "687400043d77943ef95e8417cb44e1673ee57844" + "reference": "0815f47bdd867b816b4bf2ca1c7bd7f89e1527ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/687400043d77943ef95e8417cb44e1673ee57844", - "reference": "687400043d77943ef95e8417cb44e1673ee57844", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/0815f47bdd867b816b4bf2ca1c7bd7f89e1527ca", + "reference": "0815f47bdd867b816b4bf2ca1c7bd7f89e1527ca", "shasum": "" }, "require": { @@ -12537,9 +12403,9 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.23.3" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.23.4" }, - "time": "2024-10-29T12:24:25+00:00" + "time": "2024-12-05T10:36:51+00:00" }, { "name": "mockery/mockery", @@ -12783,38 +12649,38 @@ }, { "name": "pestphp/pest", - "version": "v3.5.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/pestphp/pest.git", - "reference": "179d46ce97d52bcb3f791449ae94025c3f32e3e3" + "reference": "9688b83a3d7d0acdda21c01b8aeb933ec9fcd556" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/179d46ce97d52bcb3f791449ae94025c3f32e3e3", - "reference": "179d46ce97d52bcb3f791449ae94025c3f32e3e3", + "url": "https://api.github.com/repos/pestphp/pest/zipball/9688b83a3d7d0acdda21c01b8aeb933ec9fcd556", + "reference": "9688b83a3d7d0acdda21c01b8aeb933ec9fcd556", "shasum": "" }, "require": { - "brianium/paratest": "^7.6.0", + "brianium/paratest": "^7.6.2", "nunomaduro/collision": "^8.5.0", - "nunomaduro/termwind": "^2.2.0", + "nunomaduro/termwind": "^2.3.0", "pestphp/pest-plugin": "^3.0.0", "pestphp/pest-plugin-arch": "^3.0.0", "pestphp/pest-plugin-mutate": "^3.0.5", "php": "^8.2.0", - "phpunit/phpunit": "^11.4.3" + "phpunit/phpunit": "^11.5.0" }, "conflict": { "filp/whoops": "<2.16.0", - "phpunit/phpunit": ">11.4.3", + "phpunit/phpunit": ">11.5.0", "sebastian/exporter": "<6.0.0", "webmozart/assert": "<1.11.0" }, "require-dev": { "pestphp/pest-dev-tools": "^3.3.0", - "pestphp/pest-plugin-type-coverage": "^3.1.0", - "symfony/process": "^7.1.6" + "pestphp/pest-plugin-type-coverage": "^3.2.0", + "symfony/process": "^7.2.0" }, "bin": [ "bin/pest" @@ -12879,7 +12745,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v3.5.1" + "source": "https://github.com/pestphp/pest/tree/v3.7.0" }, "funding": [ { @@ -12891,7 +12757,7 @@ "type": "github" } ], - "time": "2024-10-31T16:12:45+00:00" + "time": "2024-12-10T11:54:49+00:00" }, { "name": "pestphp/pest-plugin", @@ -13225,16 +13091,16 @@ }, { "name": "php-webdriver/webdriver", - "version": "1.15.1", + "version": "1.15.2", "source": { "type": "git", "url": "https://github.com/php-webdriver/php-webdriver.git", - "reference": "cd52d9342c5aa738c2e75a67e47a1b6df97154e8" + "reference": "998e499b786805568deaf8cbf06f4044f05d91bf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/cd52d9342c5aa738c2e75a67e47a1b6df97154e8", - "reference": "cd52d9342c5aa738c2e75a67e47a1b6df97154e8", + "url": "https://api.github.com/repos/php-webdriver/php-webdriver/zipball/998e499b786805568deaf8cbf06f4044f05d91bf", + "reference": "998e499b786805568deaf8cbf06f4044f05d91bf", "shasum": "" }, "require": { @@ -13256,7 +13122,7 @@ "php-parallel-lint/php-parallel-lint": "^1.2", "phpunit/phpunit": "^9.3", "squizlabs/php_codesniffer": "^3.5", - "symfony/var-dumper": "^5.0 || ^6.0" + "symfony/var-dumper": "^5.0 || ^6.0 || ^7.0" }, "suggest": { "ext-SimpleXML": "For Firefox profile creation" @@ -13285,22 +13151,80 @@ ], "support": { "issues": "https://github.com/php-webdriver/php-webdriver/issues", - "source": "https://github.com/php-webdriver/php-webdriver/tree/1.15.1" + "source": "https://github.com/php-webdriver/php-webdriver/tree/1.15.2" }, - "time": "2023-10-20T12:21:20+00:00" + "time": "2024-11-21T15:12:59+00:00" + }, + { + "name": "phpstan/phpstan", + "version": "1.12.12", + "source": { + "type": "git", + "url": "https://github.com/phpstan/phpstan.git", + "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", + "reference": "b5ae1b88f471d3fd4ba1aa0046234b5ca3776dd0", + "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-28T22:13:23+00:00" }, { "name": "phpunit/php-code-coverage", - "version": "11.0.7", + "version": "11.0.8", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca" + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7f08030e8811582cc459871d28d6f5a1a4d35ca", - "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/418c59fd080954f8c4aa5631d9502ecda2387118", + "reference": "418c59fd080954f8c4aa5631d9502ecda2387118", "shasum": "" }, "require": { @@ -13319,7 +13243,7 @@ "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^11.4.1" + "phpunit/phpunit": "^11.5.0" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -13357,7 +13281,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.7" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.8" }, "funding": [ { @@ -13365,7 +13289,7 @@ "type": "github" } ], - "time": "2024-10-09T06:21:38+00:00" + "time": "2024-12-11T12:34:27+00:00" }, { "name": "phpunit/php-file-iterator", @@ -13614,16 +13538,16 @@ }, { "name": "phpunit/phpunit", - "version": "11.4.3", + "version": "11.5.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "e8e8ed1854de5d36c088ec1833beae40d2dedd76" + "reference": "0569902506a6c0878930b87ea79ec3b50ea563f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e8e8ed1854de5d36c088ec1833beae40d2dedd76", - "reference": "e8e8ed1854de5d36c088ec1833beae40d2dedd76", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/0569902506a6c0878930b87ea79ec3b50ea563f7", + "reference": "0569902506a6c0878930b87ea79ec3b50ea563f7", "shasum": "" }, "require": { @@ -13633,7 +13557,7 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.12.0", + "myclabs/deep-copy": "^1.12.1", "phar-io/manifest": "^2.0.4", "phar-io/version": "^3.2.1", "php": ">=8.2", @@ -13644,14 +13568,15 @@ "phpunit/php-timer": "^7.0.1", "sebastian/cli-parser": "^3.0.2", "sebastian/code-unit": "^3.0.1", - "sebastian/comparator": "^6.1.1", + "sebastian/comparator": "^6.2.1", "sebastian/diff": "^6.0.2", "sebastian/environment": "^7.2.0", - "sebastian/exporter": "^6.1.3", + "sebastian/exporter": "^6.3.0", "sebastian/global-state": "^7.0.2", "sebastian/object-enumerator": "^6.0.1", "sebastian/type": "^5.1.0", - "sebastian/version": "^5.0.2" + "sebastian/version": "^5.0.2", + "staabm/side-effects-detector": "^1.0.5" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -13662,7 +13587,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "11.4-dev" + "dev-main": "11.5-dev" } }, "autoload": { @@ -13694,7 +13619,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/11.4.3" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.5.0" }, "funding": [ { @@ -13710,7 +13635,7 @@ "type": "tidelift" } ], - "time": "2024-10-28T13:07:50+00:00" + "time": "2024-12-06T05:57:38+00:00" }, { "name": "sebastian/cli-parser", @@ -14150,16 +14075,16 @@ }, { "name": "sebastian/exporter", - "version": "6.1.3", + "version": "6.3.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e" + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", - "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/3473f61172093b2da7de1fb5782e1f24cc036dc3", + "reference": "3473f61172093b2da7de1fb5782e1f24cc036dc3", "shasum": "" }, "require": { @@ -14168,7 +14093,7 @@ "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^11.2" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { @@ -14216,7 +14141,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/6.1.3" + "source": "https://github.com/sebastianbergmann/exporter/tree/6.3.0" }, "funding": [ { @@ -14224,7 +14149,7 @@ "type": "github" } ], - "time": "2024-07-03T04:56:19+00:00" + "time": "2024-12-05T09:17:50+00:00" }, { "name": "sebastian/global-state", @@ -14682,16 +14607,16 @@ }, { "name": "spatie/error-solutions", - "version": "1.1.1", + "version": "1.1.2", "source": { "type": "git", "url": "https://github.com/spatie/error-solutions.git", - "reference": "ae7393122eda72eed7cc4f176d1e96ea444f2d67" + "reference": "d239a65235a1eb128dfa0a4e4c4ef032ea11b541" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/error-solutions/zipball/ae7393122eda72eed7cc4f176d1e96ea444f2d67", - "reference": "ae7393122eda72eed7cc4f176d1e96ea444f2d67", + "url": "https://api.github.com/repos/spatie/error-solutions/zipball/d239a65235a1eb128dfa0a4e4c4ef032ea11b541", + "reference": "d239a65235a1eb128dfa0a4e4c4ef032ea11b541", "shasum": "" }, "require": { @@ -14744,7 +14669,7 @@ ], "support": { "issues": "https://github.com/spatie/error-solutions/issues", - "source": "https://github.com/spatie/error-solutions/tree/1.1.1" + "source": "https://github.com/spatie/error-solutions/tree/1.1.2" }, "funding": [ { @@ -14752,20 +14677,20 @@ "type": "github" } ], - "time": "2024-07-25T11:06:04+00:00" + "time": "2024-12-11T09:51:56+00:00" }, { "name": "spatie/flare-client-php", - "version": "1.8.0", + "version": "1.10.0", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122" + "reference": "140a42b2c5d59ac4ecf8f5b493386a4f2eb28272" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122", - "reference": "180f8ca4c0d0d6fc51477bd8c53ce37ab5a96122", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/140a42b2c5d59ac4ecf8f5b493386a4f2eb28272", + "reference": "140a42b2c5d59ac4ecf8f5b493386a4f2eb28272", "shasum": "" }, "require": { @@ -14813,7 +14738,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.8.0" + "source": "https://github.com/spatie/flare-client-php/tree/1.10.0" }, "funding": [ { @@ -14821,7 +14746,7 @@ "type": "github" } ], - "time": "2024-08-01T08:27:26+00:00" + "time": "2024-12-02T14:30:06+00:00" }, { "name": "spatie/ignition", @@ -14908,16 +14833,16 @@ }, { "name": "spatie/laravel-ignition", - "version": "2.8.0", + "version": "2.9.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "3c067b75bfb50574db8f7e2c3978c65eed71126c" + "reference": "62042df15314b829d0f26e02108f559018e2aad0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/3c067b75bfb50574db8f7e2c3978c65eed71126c", - "reference": "3c067b75bfb50574db8f7e2c3978c65eed71126c", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/62042df15314b829d0f26e02108f559018e2aad0", + "reference": "62042df15314b829d0f26e02108f559018e2aad0", "shasum": "" }, "require": { @@ -14948,12 +14873,12 @@ "type": "library", "extra": { "laravel": { - "providers": [ - "Spatie\\LaravelIgnition\\IgnitionServiceProvider" - ], "aliases": { "Flare": "Spatie\\LaravelIgnition\\Facades\\Flare" - } + }, + "providers": [ + "Spatie\\LaravelIgnition\\IgnitionServiceProvider" + ] } }, "autoload": { @@ -14995,30 +14920,83 @@ "type": "github" } ], - "time": "2024-06-12T15:01:18+00:00" + "time": "2024-12-02T08:43:31+00:00" + }, + { + "name": "staabm/side-effects-detector", + "version": "1.0.5", + "source": { + "type": "git", + "url": "https://github.com/staabm/side-effects-detector.git", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/staabm/side-effects-detector/zipball/d8334211a140ce329c13726d4a715adbddd0a163", + "reference": "d8334211a140ce329c13726d4a715adbddd0a163", + "shasum": "" + }, + "require": { + "ext-tokenizer": "*", + "php": "^7.4 || ^8.0" + }, + "require-dev": { + "phpstan/extension-installer": "^1.4.3", + "phpstan/phpstan": "^1.12.6", + "phpunit/phpunit": "^9.6.21", + "symfony/var-dumper": "^5.4.43", + "tomasvotruba/type-coverage": "1.0.0", + "tomasvotruba/unused-public": "1.0.0" + }, + "type": "library", + "autoload": { + "classmap": [ + "lib/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A static analysis tool to detect side effects in PHP code", + "keywords": [ + "static analysis" + ], + "support": { + "issues": "https://github.com/staabm/side-effects-detector/issues", + "source": "https://github.com/staabm/side-effects-detector/tree/1.0.5" + }, + "funding": [ + { + "url": "https://github.com/staabm", + "type": "github" + } + ], + "time": "2024-10-20T05:08:20+00:00" }, { "name": "symfony/http-client", - "version": "v7.1.8", + "version": "v7.2.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "c30d91a1deac0dc3ed5e604683cf2e1dfc635b8a" + "reference": "955e43336aff03df1e8a8e17daefabb0127a313b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/c30d91a1deac0dc3ed5e604683cf2e1dfc635b8a", - "reference": "c30d91a1deac0dc3ed5e604683cf2e1dfc635b8a", + "url": "https://api.github.com/repos/symfony/http-client/zipball/955e43336aff03df1e8a8e17daefabb0127a313b", + "reference": "955e43336aff03df1e8a8e17daefabb0127a313b", "shasum": "" }, "require": { "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/http-client-contracts": "^3.4.1", + "symfony/http-client-contracts": "~3.4.3|^3.5.1", "symfony/service-contracts": "^2.5|^3" }, "conflict": { + "amphp/amp": "<2.5", "php-http/discovery": "<1.15", "symfony/http-foundation": "<6.4" }, @@ -15029,14 +15007,14 @@ "symfony/http-client-implementation": "3.0" }, "require-dev": { - "amphp/amp": "^2.5", - "amphp/http-client": "^4.2.1", - "amphp/http-tunnel": "^1.0", + "amphp/http-client": "^4.2.1|^5.0", + "amphp/http-tunnel": "^1.0|^2.0", "amphp/socket": "^1.1", "guzzlehttp/promises": "^1.4|^2.0", "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", + "symfony/amphp-http-client-meta": "^1.0|^2.0", "symfony/dependency-injection": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", "symfony/messenger": "^6.4|^7.0", @@ -15073,7 +15051,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.1.8" + "source": "https://github.com/symfony/http-client/tree/v7.2.0" }, "funding": [ { @@ -15089,20 +15067,20 @@ "type": "tidelift" } ], - "time": "2024-11-13T13:40:27+00:00" + "time": "2024-11-29T08:22:02+00:00" }, { "name": "symfony/http-client-contracts", - "version": "v3.5.0", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "20414d96f391677bf80078aa55baece78b82647d" + "reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/20414d96f391677bf80078aa55baece78b82647d", - "reference": "20414d96f391677bf80078aa55baece78b82647d", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/c2f3ad828596624ca39ea40f83617ef51ca8bbf9", + "reference": "c2f3ad828596624ca39ea40f83617ef51ca8bbf9", "shasum": "" }, "require": { @@ -15110,12 +15088,12 @@ }, "type": "library", "extra": { + "thanks": { + "url": "https://github.com/symfony/contracts", + "name": "symfony/contracts" + }, "branch-alias": { "dev-main": "3.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" } }, "autoload": { @@ -15151,7 +15129,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.5.1" }, "funding": [ { @@ -15167,7 +15145,7 @@ "type": "tidelift" } ], - "time": "2024-04-18T09:32:20+00:00" + "time": "2024-11-25T12:02:18+00:00" }, { "name": "ta-tikoma/phpunit-architecture-test", @@ -15281,12 +15259,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { "php": "^8.2" }, - "platform-dev": [], + "platform-dev": {}, "plugin-api-version": "2.6.0" } diff --git a/config/constants.php b/config/constants.php index f7d5a78319..a02d6616aa 100644 --- a/config/constants.php +++ b/config/constants.php @@ -2,7 +2,7 @@ return [ 'coolify' => [ - 'version' => '4.0.0-beta.376', + 'version' => '4.0.0-beta.377', 'self_hosted' => env('SELF_HOSTED', true), 'autoupdate' => env('AUTOUPDATE'), 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), @@ -33,6 +33,14 @@ 'app_key' => env('PUSHER_APP_KEY'), ], + 'migration' => [ + 'is_migration_enabled' => env('MIGRATION_ENABLED', true), + ], + + 'seeder' => [ + 'is_seeder_enabled' => env('SEEDER_ENABLED', true), + ], + 'horizon' => [ 'is_horizon_enabled' => env('HORIZON_ENABLED', true), 'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true), @@ -77,11 +85,6 @@ ], ], - 'waitlist' => [ - 'enabled' => env('WAITLIST', false), - 'expiration' => 10, - ], - 'sentry' => [ 'sentry_dsn' => env('SENTRY_DSN'), ], diff --git a/config/services.php b/config/services.php index 9fd55870f2..509e737569 100644 --- a/config/services.php +++ b/config/services.php @@ -30,12 +30,4 @@ 'secret' => env('AWS_SECRET_ACCESS_KEY'), 'region' => env('AWS_DEFAULT_REGION', 'us-east-1'), ], - - 'azure' => [ - 'client_id' => env('AZURE_CLIENT_ID'), - 'client_secret' => env('AZURE_CLIENT_SECRET'), - 'redirect' => env('AZURE_REDIRECT_URI'), - 'tenant' => env('AZURE_TENANT_ID'), - 'proxy' => env('AZURE_PROXY'), - ], ]; diff --git a/database/migrations/2024_06_11_081614_add_www_non_www_redirect.php b/database/migrations/2024_06_11_081614_add_www_non_www_redirect.php index 21ee4efcc2..231c09ceba 100644 --- a/database/migrations/2024_06_11_081614_add_www_non_www_redirect.php +++ b/database/migrations/2024_06_11_081614_add_www_non_www_redirect.php @@ -12,7 +12,7 @@ public function up(): void { Schema::table('applications', function (Blueprint $table) { - $table->string('redirect')->enum('www', 'non-www', 'both')->default('both')->after('domain'); + $table->enum('redirect', ['www', 'non-www', 'both'])->default('both')->after('domain'); }); } diff --git a/database/migrations/2024_10_30_074601_rename_token_permissions.php b/database/migrations/2024_10_30_074601_rename_token_permissions.php new file mode 100644 index 0000000000..69d774d7f7 --- /dev/null +++ b/database/migrations/2024_10_30_074601_rename_token_permissions.php @@ -0,0 +1,60 @@ +abilities)) { + $abilities->push('root'); + } + if (in_array('read-only', $token->abilities)) { + $abilities->push('read'); + } + if (in_array('view:sensitive', $token->abilities)) { + $abilities->push('read', 'read:sensitive'); + } + $token->abilities = $abilities->unique()->values()->all(); + $token->save(); + } + } catch (\Exception $e) { + \Log::error('Error renaming token permissions: '.$e->getMessage()); + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + try { + $tokens = PersonalAccessToken::all(); + foreach ($tokens as $token) { + $abilities = collect(); + if (in_array('root', $token->abilities)) { + $abilities->push('*'); + } else { + if (in_array('read', $token->abilities)) { + $abilities->push('read-only'); + } + if (in_array('read:sensitive', $token->abilities)) { + $abilities->push('view:sensitive'); + } + } + $token->abilities = $abilities->unique()->values()->all(); + $token->save(); + } + } catch (\Exception $e) { + \Log::error('Error renaming token permissions: '.$e->getMessage()); + } + } +}; diff --git a/database/migrations/2024_12_05_091823_add_disable_build_cache_advanced_option.php b/database/migrations/2024_12_05_091823_add_disable_build_cache_advanced_option.php new file mode 100644 index 0000000000..751342302d --- /dev/null +++ b/database/migrations/2024_12_05_091823_add_disable_build_cache_advanced_option.php @@ -0,0 +1,22 @@ +boolean('disable_build_cache')->default(false); + }); + } + + public function down(): void + { + Schema::table('application_settings', function (Blueprint $table) { + $table->dropColumn('disable_build_cache'); + }); + } +}; diff --git a/database/migrations/2024_12_05_212355_create_email_notification_settings_table.php b/database/migrations/2024_12_05_212355_create_email_notification_settings_table.php new file mode 100644 index 0000000000..7338a8d0d2 --- /dev/null +++ b/database/migrations/2024_12_05_212355_create_email_notification_settings_table.php @@ -0,0 +1,58 @@ +id(); + $table->foreignId('team_id')->constrained()->cascadeOnDelete(); + + $table->boolean('smtp_enabled')->default(false); + $table->text('smtp_from_address')->nullable(); + $table->text('smtp_from_name')->nullable(); + $table->text('smtp_recipients')->nullable(); + $table->text('smtp_host')->nullable(); + $table->integer('smtp_port')->nullable(); + $table->string('smtp_encryption')->nullable(); + $table->text('smtp_username')->nullable(); + $table->text('smtp_password')->nullable(); + $table->integer('smtp_timeout')->nullable(); + + $table->boolean('resend_enabled')->default(false); + $table->text('resend_api_key')->nullable(); + + $table->boolean('use_instance_email_settings')->default(false); + + $table->boolean('deployment_success_email_notifications')->default(false); + $table->boolean('deployment_failure_email_notifications')->default(true); + $table->boolean('status_change_email_notifications')->default(false); + $table->boolean('backup_success_email_notifications')->default(false); + $table->boolean('backup_failure_email_notifications')->default(true); + $table->boolean('scheduled_task_success_email_notifications')->default(false); + $table->boolean('scheduled_task_failure_email_notifications')->default(true); + $table->boolean('docker_cleanup_success_email_notifications')->default(false); + $table->boolean('docker_cleanup_failure_email_notifications')->default(true); + $table->boolean('server_disk_usage_email_notifications')->default(true); + $table->boolean('server_reachable_email_notifications')->default(false); + $table->boolean('server_unreachable_email_notifications')->default(true); + + $table->unique(['team_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('email_notification_settings'); + } +}; diff --git a/database/migrations/2024_12_05_212416_create_discord_notification_settings_table.php b/database/migrations/2024_12_05_212416_create_discord_notification_settings_table.php new file mode 100644 index 0000000000..0f2ced67f0 --- /dev/null +++ b/database/migrations/2024_12_05_212416_create_discord_notification_settings_table.php @@ -0,0 +1,45 @@ +id(); + $table->foreignId('team_id')->constrained()->cascadeOnDelete(); + + $table->boolean('discord_enabled')->default(false); + $table->text('discord_webhook_url')->nullable(); + + $table->boolean('deployment_success_discord_notifications')->default(false); + $table->boolean('deployment_failure_discord_notifications')->default(true); + $table->boolean('status_change_discord_notifications')->default(false); + $table->boolean('backup_success_discord_notifications')->default(false); + $table->boolean('backup_failure_discord_notifications')->default(true); + $table->boolean('scheduled_task_success_discord_notifications')->default(false); + $table->boolean('scheduled_task_failure_discord_notifications')->default(true); + $table->boolean('docker_cleanup_success_discord_notifications')->default(false); + $table->boolean('docker_cleanup_failure_discord_notifications')->default(true); + $table->boolean('server_disk_usage_discord_notifications')->default(true); + $table->boolean('server_reachable_discord_notifications')->default(false); + $table->boolean('server_unreachable_discord_notifications')->default(true); + + $table->unique(['team_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('discord_notification_settings'); + } +}; diff --git a/database/migrations/2024_12_05_212440_create_telegram_notification_settings_table.php b/database/migrations/2024_12_05_212440_create_telegram_notification_settings_table.php new file mode 100644 index 0000000000..db4d998e37 --- /dev/null +++ b/database/migrations/2024_12_05_212440_create_telegram_notification_settings_table.php @@ -0,0 +1,59 @@ +id(); + $table->foreignId('team_id')->constrained()->cascadeOnDelete(); + + $table->boolean('telegram_enabled')->default(false); + $table->text('telegram_token')->nullable(); + $table->text('telegram_chat_id')->nullable(); + + $table->boolean('deployment_success_telegram_notifications')->default(false); + $table->boolean('deployment_failure_telegram_notifications')->default(true); + $table->boolean('status_change_telegram_notifications')->default(false); + $table->boolean('backup_success_telegram_notifications')->default(false); + $table->boolean('backup_failure_telegram_notifications')->default(true); + $table->boolean('scheduled_task_success_telegram_notifications')->default(false); + $table->boolean('scheduled_task_failure_telegram_notifications')->default(true); + $table->boolean('docker_cleanup_success_telegram_notifications')->default(false); + $table->boolean('docker_cleanup_failure_telegram_notifications')->default(true); + $table->boolean('server_disk_usage_telegram_notifications')->default(true); + $table->boolean('server_reachable_telegram_notifications')->default(false); + $table->boolean('server_unreachable_telegram_notifications')->default(true); + + $table->text('telegram_notifications_deployment_success_thread_id')->nullable(); + $table->text('telegram_notifications_deployment_failure_thread_id')->nullable(); + $table->text('telegram_notifications_status_change_thread_id')->nullable(); + $table->text('telegram_notifications_backup_success_thread_id')->nullable(); + $table->text('telegram_notifications_backup_failure_thread_id')->nullable(); + $table->text('telegram_notifications_scheduled_task_success_thread_id')->nullable(); + $table->text('telegram_notifications_scheduled_task_failure_thread_id')->nullable(); + $table->text('telegram_notifications_docker_cleanup_success_thread_id')->nullable(); + $table->text('telegram_notifications_docker_cleanup_failure_thread_id')->nullable(); + $table->text('telegram_notifications_server_disk_usage_thread_id')->nullable(); + $table->text('telegram_notifications_server_reachable_thread_id')->nullable(); + $table->text('telegram_notifications_server_unreachable_thread_id')->nullable(); + + $table->unique(['team_id']); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('telegram_notification_settings'); + } +}; diff --git a/database/migrations/2024_12_05_212546_migrate_email_notification_settings_from_teams_table.php b/database/migrations/2024_12_05_212546_migrate_email_notification_settings_from_teams_table.php new file mode 100644 index 0000000000..384f62f065 --- /dev/null +++ b/database/migrations/2024_12_05_212546_migrate_email_notification_settings_from_teams_table.php @@ -0,0 +1,135 @@ +get(); + + foreach ($teams as $team) { + try { + DB::table('email_notification_settings')->updateOrInsert( + ['team_id' => $team->id], + [ + 'smtp_enabled' => $team->smtp_enabled ?? false, + 'smtp_from_address' => $team->smtp_from_address ? Crypt::encryptString($team->smtp_from_address) : null, + 'smtp_from_name' => $team->smtp_from_name ? Crypt::encryptString($team->smtp_from_name) : null, + 'smtp_recipients' => $team->smtp_recipients ? Crypt::encryptString($team->smtp_recipients) : null, + 'smtp_host' => $team->smtp_host ? Crypt::encryptString($team->smtp_host) : null, + 'smtp_port' => $team->smtp_port, + 'smtp_encryption' => $team->smtp_encryption, + 'smtp_username' => $team->smtp_username ? Crypt::encryptString($team->smtp_username) : null, + 'smtp_password' => $team->smtp_password, + 'smtp_timeout' => $team->smtp_timeout, + + 'use_instance_email_settings' => $team->use_instance_email_settings ?? false, + + 'resend_enabled' => $team->resend_enabled ?? false, + 'resend_api_key' => $team->resend_api_key, + + 'deployment_success_email_notifications' => $team->smtp_notifications_deployments ?? false, + 'deployment_failure_email_notifications' => $team->smtp_notifications_deployments ?? true, + 'backup_success_email_notifications' => $team->smtp_notifications_database_backups ?? false, + 'backup_failure_email_notifications' => $team->smtp_notifications_database_backups ?? true, + 'scheduled_task_success_email_notifications' => $team->smtp_notifications_scheduled_tasks ?? false, + 'scheduled_task_failure_email_notifications' => $team->smtp_notifications_scheduled_tasks ?? true, + 'status_change_email_notifications' => $team->smtp_notifications_status_changes ?? false, + 'server_disk_usage_email_notifications' => $team->smtp_notifications_server_disk_usage ?? true, + ] + ); + } catch (Exception $e) { + \Log::error('Error migrating email notification settings from teams table: '.$e->getMessage()); + } + } + + Schema::table('teams', function (Blueprint $table) { + $table->dropColumn([ + 'smtp_enabled', + 'smtp_from_address', + 'smtp_from_name', + 'smtp_recipients', + 'smtp_host', + 'smtp_port', + 'smtp_encryption', + 'smtp_username', + 'smtp_password', + 'smtp_timeout', + 'use_instance_email_settings', + 'resend_enabled', + 'resend_api_key', + 'smtp_notifications_test', + 'smtp_notifications_deployments', + 'smtp_notifications_database_backups', + 'smtp_notifications_scheduled_tasks', + 'smtp_notifications_status_changes', + 'smtp_notifications_server_disk_usage', + ]); + }); + } + + public function down(): void + { + Schema::table('teams', function (Blueprint $table) { + $table->boolean('smtp_enabled')->default(false); + $table->string('smtp_from_address')->nullable(); + $table->string('smtp_from_name')->nullable(); + $table->string('smtp_recipients')->nullable(); + $table->string('smtp_host')->nullable(); + $table->integer('smtp_port')->nullable(); + $table->string('smtp_encryption')->nullable(); + $table->text('smtp_username')->nullable(); + $table->text('smtp_password')->nullable(); + $table->integer('smtp_timeout')->nullable(); + + $table->boolean('use_instance_email_settings')->default(false); + $table->boolean('resend_enabled')->default(false); + + $table->text('resend_api_key')->nullable(); + + $table->boolean('smtp_notifications_test')->default(false); + $table->boolean('smtp_notifications_deployments')->default(false); + $table->boolean('smtp_notifications_database_backups')->default(true); + $table->boolean('smtp_notifications_scheduled_tasks')->default(false); + $table->boolean('smtp_notifications_status_changes')->default(false); + $table->boolean('smtp_notifications_server_disk_usage')->default(true); + }); + + $settings = DB::table('email_notification_settings')->get(); + foreach ($settings as $setting) { + try { + DB::table('teams') + ->where('id', $setting->team_id) + ->update([ + 'smtp_enabled' => $setting->smtp_enabled, + 'smtp_from_address' => $setting->smtp_from_address ? Crypt::decryptString($setting->smtp_from_address) : null, + 'smtp_from_name' => $setting->smtp_from_name ? Crypt::decryptString($setting->smtp_from_name) : null, + 'smtp_recipients' => $setting->smtp_recipients ? Crypt::decryptString($setting->smtp_recipients) : null, + 'smtp_host' => $setting->smtp_host ? Crypt::decryptString($setting->smtp_host) : null, + 'smtp_port' => $setting->smtp_port, + 'smtp_encryption' => $setting->smtp_encryption, + 'smtp_username' => $setting->smtp_username ? Crypt::decryptString($setting->smtp_username) : null, + 'smtp_password' => $setting->smtp_password, + 'smtp_timeout' => $setting->smtp_timeout, + + 'use_instance_email_settings' => $setting->use_instance_email_settings, + + 'resend_enabled' => $setting->resend_enabled, + 'resend_api_key' => $setting->resend_api_key, + + 'smtp_notifications_deployments' => $setting->deployment_success_email_notifications || $setting->deployment_failure_email_notifications, + 'smtp_notifications_database_backups' => $setting->backup_success_email_notifications || $setting->backup_failure_email_notifications, + 'smtp_notifications_scheduled_tasks' => $setting->scheduled_task_success_email_notifications || $setting->scheduled_task_failure_email_notifications, + 'smtp_notifications_status_changes' => $setting->status_change_email_notifications, + ]); + } catch (Exception $e) { + \Log::error('Error migrating email notification settings from teams table: '.$e->getMessage()); + } + } + } +}; diff --git a/database/migrations/2024_12_05_212631_migrate_discord_notification_settings_from_teams_table.php b/database/migrations/2024_12_05_212631_migrate_discord_notification_settings_from_teams_table.php new file mode 100644 index 0000000000..ed9e9af82a --- /dev/null +++ b/database/migrations/2024_12_05_212631_migrate_discord_notification_settings_from_teams_table.php @@ -0,0 +1,92 @@ +get(); + + foreach ($teams as $team) { + try { + DB::table('discord_notification_settings')->updateOrInsert( + ['team_id' => $team->id], + [ + 'discord_enabled' => $team->discord_enabled ?? false, + 'discord_webhook_url' => $team->discord_webhook_url ? Crypt::encryptString($team->discord_webhook_url) : null, + + 'deployment_success_discord_notifications' => $team->discord_notifications_deployments ?? false, + 'deployment_failure_discord_notifications' => $team->discord_notifications_deployments ?? true, + 'backup_success_discord_notifications' => $team->discord_notifications_database_backups ?? false, + 'backup_failure_discord_notifications' => $team->discord_notifications_database_backups ?? true, + 'scheduled_task_success_discord_notifications' => $team->discord_notifications_scheduled_tasks ?? false, + 'scheduled_task_failure_discord_notifications' => $team->discord_notifications_scheduled_tasks ?? true, + 'status_change_discord_notifications' => $team->discord_notifications_status_changes ?? false, + 'server_disk_usage_discord_notifications' => $team->discord_notifications_server_disk_usage ?? true, + ] + ); + } catch (Exception $e) { + \Log::error('Error migrating discord notification settings from teams table: '.$e->getMessage()); + } + } + + Schema::table('teams', function (Blueprint $table) { + $table->dropColumn([ + 'discord_enabled', + 'discord_webhook_url', + 'discord_notifications_test', + 'discord_notifications_deployments', + 'discord_notifications_status_changes', + 'discord_notifications_database_backups', + 'discord_notifications_scheduled_tasks', + 'discord_notifications_server_disk_usage', + ]); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('teams', function (Blueprint $table) { + $table->boolean('discord_enabled')->default(false); + $table->string('discord_webhook_url')->nullable(); + + $table->boolean('discord_notifications_test')->default(true); + $table->boolean('discord_notifications_deployments')->default(true); + $table->boolean('discord_notifications_status_changes')->default(true); + $table->boolean('discord_notifications_database_backups')->default(true); + $table->boolean('discord_notifications_scheduled_tasks')->default(true); + $table->boolean('discord_notifications_server_disk_usage')->default(true); + }); + + $settings = DB::table('discord_notification_settings')->get(); + foreach ($settings as $setting) { + try { + DB::table('teams') + ->where('id', $setting->team_id) + ->update([ + 'discord_enabled' => $setting->discord_enabled, + 'discord_webhook_url' => Crypt::decryptString($setting->discord_webhook_url), + + 'discord_notifications_deployments' => $setting->deployment_success_discord_notifications || $setting->deployment_failure_discord_notifications, + 'discord_notifications_status_changes' => $setting->status_change_discord_notifications, + 'discord_notifications_database_backups' => $setting->backup_success_discord_notifications || $setting->backup_failure_discord_notifications, + 'discord_notifications_scheduled_tasks' => $setting->scheduled_task_success_discord_notifications || $setting->scheduled_task_failure_discord_notifications, + 'discord_notifications_server_disk_usage' => $setting->server_disk_usage_discord_notifications, + ]); + } catch (Exception $e) { + \Log::error('Error migrating discord notification settings from teams table: '.$e->getMessage()); + } + } + } +}; diff --git a/database/migrations/2024_12_05_212705_migrate_telegram_notification_settings_from_teams_table.php b/database/migrations/2024_12_05_212705_migrate_telegram_notification_settings_from_teams_table.php new file mode 100644 index 0000000000..df120e2733 --- /dev/null +++ b/database/migrations/2024_12_05_212705_migrate_telegram_notification_settings_from_teams_table.php @@ -0,0 +1,115 @@ +get(); + + foreach ($teams as $team) { + try { + DB::table('telegram_notification_settings')->updateOrInsert( + ['team_id' => $team->id], + [ + 'telegram_enabled' => $team->telegram_enabled ?? false, + 'telegram_token' => $team->telegram_token ? Crypt::encryptString($team->telegram_token) : null, + 'telegram_chat_id' => $team->telegram_chat_id ? Crypt::encryptString($team->telegram_chat_id) : null, + + 'deployment_success_telegram_notifications' => $team->telegram_notifications_deployments ?? false, + 'deployment_failure_telegram_notifications' => $team->telegram_notifications_deployments ?? true, + 'backup_success_telegram_notifications' => $team->telegram_notifications_database_backups ?? false, + 'backup_failure_telegram_notifications' => $team->telegram_notifications_database_backups ?? true, + 'scheduled_task_success_telegram_notifications' => $team->telegram_notifications_scheduled_tasks ?? false, + 'scheduled_task_failure_telegram_notifications' => $team->telegram_notifications_scheduled_tasks ?? true, + 'status_change_telegram_notifications' => $team->telegram_notifications_status_changes ?? false, + 'server_disk_usage_telegram_notifications' => $team->telegram_notifications_server_disk_usage ?? true, + + 'telegram_notifications_deployment_success_thread_id' => $team->telegram_notifications_deployments_message_thread_id ? Crypt::encryptString($team->telegram_notifications_deployments_message_thread_id) : null, + 'telegram_notifications_deployment_failure_thread_id' => $team->telegram_notifications_deployments_message_thread_id ? Crypt::encryptString($team->telegram_notifications_deployments_message_thread_id) : null, + 'telegram_notifications_backup_success_thread_id' => $team->telegram_notifications_database_backups_message_thread_id ? Crypt::encryptString($team->telegram_notifications_database_backups_message_thread_id) : null, + 'telegram_notifications_backup_failure_thread_id' => $team->telegram_notifications_database_backups_message_thread_id ? Crypt::encryptString($team->telegram_notifications_database_backups_message_thread_id) : null, + 'telegram_notifications_scheduled_task_success_thread_id' => $team->telegram_notifications_scheduled_tasks_thread_id ? Crypt::encryptString($team->telegram_notifications_scheduled_tasks_thread_id) : null, + 'telegram_notifications_scheduled_task_failure_thread_id' => $team->telegram_notifications_scheduled_tasks_thread_id ? Crypt::encryptString($team->telegram_notifications_scheduled_tasks_thread_id) : null, + 'telegram_notifications_status_change_thread_id' => $team->telegram_notifications_status_changes_message_thread_id ? Crypt::encryptString($team->telegram_notifications_status_changes_message_thread_id) : null, + ] + ); + } catch (Exception $e) { + Log::error('Error migrating telegram notification settings from teams table: '.$e->getMessage()); + } + } + + Schema::table('teams', function (Blueprint $table) { + $table->dropColumn([ + 'telegram_enabled', + 'telegram_token', + 'telegram_chat_id', + 'telegram_notifications_test', + 'telegram_notifications_deployments', + 'telegram_notifications_status_changes', + 'telegram_notifications_database_backups', + 'telegram_notifications_scheduled_tasks', + 'telegram_notifications_server_disk_usage', + 'telegram_notifications_test_message_thread_id', + 'telegram_notifications_deployments_message_thread_id', + 'telegram_notifications_status_changes_message_thread_id', + 'telegram_notifications_database_backups_message_thread_id', + 'telegram_notifications_scheduled_tasks_thread_id', + ]); + }); + } + + public function down(): void + { + Schema::table('teams', function (Blueprint $table) { + $table->boolean('telegram_enabled')->default(false); + $table->text('telegram_token')->nullable(); + $table->text('telegram_chat_id')->nullable(); + + $table->boolean('telegram_notifications_test')->default(true); + $table->boolean('telegram_notifications_deployments')->default(true); + $table->boolean('telegram_notifications_status_changes')->default(true); + $table->boolean('telegram_notifications_database_backups')->default(true); + $table->boolean('telegram_notifications_scheduled_tasks')->default(true); + $table->boolean('telegram_notifications_server_disk_usage')->default(true); + + $table->text('telegram_notifications_test_message_thread_id')->nullable(); + $table->text('telegram_notifications_deployments_message_thread_id')->nullable(); + $table->text('telegram_notifications_status_changes_message_thread_id')->nullable(); + $table->text('telegram_notifications_database_backups_message_thread_id')->nullable(); + $table->text('telegram_notifications_scheduled_tasks_thread_id')->nullable(); + }); + + $settings = DB::table('telegram_notification_settings')->get(); + foreach ($settings as $setting) { + try { + DB::table('teams') + ->where('id', $setting->team_id) + ->update([ + 'telegram_enabled' => $setting->telegram_enabled, + 'telegram_token' => $setting->telegram_token ? Crypt::decryptString($setting->telegram_token) : null, + 'telegram_chat_id' => $setting->telegram_chat_id ? Crypt::decryptString($setting->telegram_chat_id) : null, + + 'telegram_notifications_deployments' => $setting->deployment_success_telegram_notifications || $setting->deployment_failure_telegram_notifications, + 'telegram_notifications_status_changes' => $setting->status_change_telegram_notifications, + 'telegram_notifications_database_backups' => $setting->backup_success_telegram_notifications || $setting->backup_failure_telegram_notifications, + 'telegram_notifications_scheduled_tasks' => $setting->scheduled_task_success_telegram_notifications || $setting->scheduled_task_failure_telegram_notifications, + 'telegram_notifications_server_disk_usage' => $setting->server_disk_usage_telegram_notifications, + + 'telegram_notifications_deployments_message_thread_id' => $setting->telegram_notifications_deployment_success_thread_id ? Crypt::decryptString($setting->telegram_notifications_deployment_success_thread_id) : null, + 'telegram_notifications_status_changes_message_thread_id' => $setting->telegram_notifications_status_change_thread_id ? Crypt::decryptString($setting->telegram_notifications_status_change_thread_id) : null, + 'telegram_notifications_database_backups_message_thread_id' => $setting->telegram_notifications_backup_success_thread_id ? Crypt::decryptString($setting->telegram_notifications_backup_success_thread_id) : null, + 'telegram_notifications_scheduled_tasks_thread_id' => $setting->telegram_notifications_scheduled_task_success_thread_id ? Crypt::decryptString($setting->telegram_notifications_scheduled_task_success_thread_id) : null, + ]); + } catch (Exception $e) { + Log::error('Error migrating telegram notification settings from teams table: '.$e->getMessage()); + } + } + } +}; diff --git a/database/migrations/2024_12_06_142014_create_slack_notification_settings_table.php b/database/migrations/2024_12_06_142014_create_slack_notification_settings_table.php new file mode 100644 index 0000000000..8aee40d87f --- /dev/null +++ b/database/migrations/2024_12_06_142014_create_slack_notification_settings_table.php @@ -0,0 +1,58 @@ +id(); + $table->foreignId('team_id')->constrained()->cascadeOnDelete(); + + $table->boolean('slack_enabled')->default(false); + $table->text('slack_webhook_url')->nullable(); + + $table->boolean('deployment_success_slack_notifications')->default(false); + $table->boolean('deployment_failure_slack_notifications')->default(true); + $table->boolean('status_change_slack_notifications')->default(false); + $table->boolean('backup_success_slack_notifications')->default(false); + $table->boolean('backup_failure_slack_notifications')->default(true); + $table->boolean('scheduled_task_success_slack_notifications')->default(false); + $table->boolean('scheduled_task_failure_slack_notifications')->default(true); + $table->boolean('docker_cleanup_success_slack_notifications')->default(false); + $table->boolean('docker_cleanup_failure_slack_notifications')->default(true); + $table->boolean('server_disk_usage_slack_notifications')->default(true); + $table->boolean('server_reachable_slack_notifications')->default(false); + $table->boolean('server_unreachable_slack_notifications')->default(true); + + $table->unique(['team_id']); + }); + $teams = DB::table('teams')->get(); + + foreach ($teams as $team) { + try { + DB::table('slack_notification_settings')->insert([ + 'team_id' => $team->id, + ]); + } catch (\Throwable $e) { + Log::error('Error creating slack notification settings for existing teams: '.$e->getMessage()); + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('slack_notification_settings'); + } +}; diff --git a/database/migrations/2024_12_09_105711_drop_waitlists_table.php b/database/migrations/2024_12_09_105711_drop_waitlists_table.php new file mode 100644 index 0000000000..0e319369d0 --- /dev/null +++ b/database/migrations/2024_12_09_105711_drop_waitlists_table.php @@ -0,0 +1,31 @@ +id(); + $table->string('uuid'); + $table->string('type'); + $table->string('email')->unique(); + $table->boolean('verified')->default(false); + $table->timestamps(); + }); + } +}; diff --git a/database/migrations/2024_12_10_122142_encrypt_instance_settings_email_columns.php b/database/migrations/2024_12_10_122142_encrypt_instance_settings_email_columns.php new file mode 100644 index 0000000000..5602e0ae94 --- /dev/null +++ b/database/migrations/2024_12_10_122142_encrypt_instance_settings_email_columns.php @@ -0,0 +1,72 @@ +exists()) { + Schema::table('instance_settings', function (Blueprint $table) { + $table->text('smtp_from_address')->nullable()->change(); + $table->text('smtp_from_name')->nullable()->change(); + $table->text('smtp_recipients')->nullable()->change(); + $table->text('smtp_host')->nullable()->change(); + $table->text('smtp_username')->nullable()->change(); + }); + + $settings = DB::table('instance_settings')->get(); + foreach ($settings as $setting) { + try { + DB::table('instance_settings')->where('id', $setting->id)->update([ + 'smtp_from_address' => $setting->smtp_from_address ? Crypt::encryptString($setting->smtp_from_address) : null, + 'smtp_from_name' => $setting->smtp_from_name ? Crypt::encryptString($setting->smtp_from_name) : null, + 'smtp_recipients' => $setting->smtp_recipients ? Crypt::encryptString($setting->smtp_recipients) : null, + 'smtp_host' => $setting->smtp_host ? Crypt::encryptString($setting->smtp_host) : null, + 'smtp_username' => $setting->smtp_username ? Crypt::encryptString($setting->smtp_username) : null, + ]); + } catch (Exception $e) { + \Log::error('Error encrypting instance settings email columns: '.$e->getMessage()); + } + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('instance_settings', function (Blueprint $table) { + $table->text('smtp_from_address')->nullable()->change(); + $table->text('smtp_from_name')->nullable()->change(); + $table->text('smtp_recipients')->nullable()->change(); + $table->text('smtp_host')->nullable()->change(); + $table->text('smtp_username')->nullable()->change(); + }); + + if (DB::table('instance_settings')->exists()) { + $settings = DB::table('instance_settings')->get(); + foreach ($settings as $setting) { + try { + DB::table('instance_settings')->where('id', $setting->id)->update([ + 'smtp_from_address' => $setting->smtp_from_address ? Crypt::decryptString($setting->smtp_from_address) : null, + 'smtp_from_name' => $setting->smtp_from_name ? Crypt::decryptString($setting->smtp_from_name) : null, + 'smtp_recipients' => $setting->smtp_recipients ? Crypt::decryptString($setting->smtp_recipients) : null, + 'smtp_host' => $setting->smtp_host ? Crypt::decryptString($setting->smtp_host) : null, + 'smtp_username' => $setting->smtp_username ? Crypt::decryptString($setting->smtp_username) : null, + ]); + } catch (Exception $e) { + \Log::error('Error decrypting instance settings email columns: '.$e->getMessage()); + } + } + } + } +}; diff --git a/database/migrations/2024_12_10_122143_drop_resale_license.php b/database/migrations/2024_12_10_122143_drop_resale_license.php new file mode 100644 index 0000000000..aaf498c3bd --- /dev/null +++ b/database/migrations/2024_12_10_122143_drop_resale_license.php @@ -0,0 +1,30 @@ +dropColumn('is_resale_license_active'); + $table->dropColumn('resale_license'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('instance_settings', function (Blueprint $table) { + $table->boolean('is_resale_license_active')->default(false); + $table->longText('resale_license')->nullable(); + }); + } +}; diff --git a/database/migrations/2024_12_11_135026_create_pushover_notification_settings_table.php b/database/migrations/2024_12_11_135026_create_pushover_notification_settings_table.php new file mode 100644 index 0000000000..ad4215a075 --- /dev/null +++ b/database/migrations/2024_12_11_135026_create_pushover_notification_settings_table.php @@ -0,0 +1,59 @@ +id(); + $table->foreignId('team_id')->constrained()->cascadeOnDelete(); + + $table->boolean('pushover_enabled')->default(false); + $table->text('pushover_user_key')->nullable(); + $table->text('pushover_api_token')->nullable(); + + $table->boolean('deployment_success_pushover_notifications')->default(false); + $table->boolean('deployment_failure_pushover_notifications')->default(true); + $table->boolean('status_change_pushover_notifications')->default(false); + $table->boolean('backup_success_pushover_notifications')->default(false); + $table->boolean('backup_failure_pushover_notifications')->default(true); + $table->boolean('scheduled_task_success_pushover_notifications')->default(false); + $table->boolean('scheduled_task_failure_pushover_notifications')->default(true); + $table->boolean('docker_cleanup_success_pushover_notifications')->default(false); + $table->boolean('docker_cleanup_failure_pushover_notifications')->default(true); + $table->boolean('server_disk_usage_pushover_notifications')->default(true); + $table->boolean('server_reachable_pushover_notifications')->default(false); + $table->boolean('server_unreachable_pushover_notifications')->default(true); + + $table->unique(['team_id']); + }); + $teams = DB::table('teams')->get(); + + foreach ($teams as $team) { + try { + DB::table('pushover_notification_settings')->insert([ + 'team_id' => $team->id, + ]); + } catch (\Throwable $e) { + Log::error('Error creating pushover notification settings for existing teams: '.$e->getMessage()); + } + } + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('pushover_notification_settings'); + } +}; diff --git a/database/migrations/2024_12_11_161418_add_authentik_base_url_to_oauth_settings_table.php b/database/migrations/2024_12_11_161418_add_authentik_base_url_to_oauth_settings_table.php new file mode 100644 index 0000000000..44ecb0cde3 --- /dev/null +++ b/database/migrations/2024_12_11_161418_add_authentik_base_url_to_oauth_settings_table.php @@ -0,0 +1,28 @@ +string('base_url')->nullable(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('oauth_settings', function (Blueprint $table) { + $table->dropColumn('base_url'); + }); + } +}; diff --git a/database/seeders/GithubAppSeeder.php b/database/seeders/GithubAppSeeder.php index 2ece7a05b9..3cfb82e648 100644 --- a/database/seeders/GithubAppSeeder.php +++ b/database/seeders/GithubAppSeeder.php @@ -23,6 +23,7 @@ public function run(): void GithubApp::create([ 'name' => 'coolify-laravel-development-public', 'uuid' => '69420', + 'organization' => 'coollabsio', 'api_url' => 'https://api.github.com', 'html_url' => 'https://github.com', 'is_public' => false, diff --git a/database/seeders/InstanceSettingsSeeder.php b/database/seeders/InstanceSettingsSeeder.php index 35fc8506ba..7f2deb3a6b 100644 --- a/database/seeders/InstanceSettingsSeeder.php +++ b/database/seeders/InstanceSettingsSeeder.php @@ -16,7 +16,6 @@ public function run(): void InstanceSettings::create([ 'id' => 0, 'is_registration_enabled' => true, - 'is_resale_license_active' => true, 'smtp_enabled' => true, 'smtp_host' => 'coolify-mail', 'smtp_port' => 1025, diff --git a/database/seeders/OauthSettingSeeder.php b/database/seeders/OauthSettingSeeder.php index 16abf9e04b..bf902175fc 100644 --- a/database/seeders/OauthSettingSeeder.php +++ b/database/seeders/OauthSettingSeeder.php @@ -12,25 +12,62 @@ class OauthSettingSeeder extends Seeder */ public function run(): void { - OauthSetting::firstOrCreate([ - 'id' => 0, - 'provider' => 'azure', - ]); - OauthSetting::firstOrCreate([ - 'id' => 1, - 'provider' => 'bitbucket', - ]); - OauthSetting::firstOrCreate([ - 'id' => 2, - 'provider' => 'github', - ]); - OauthSetting::firstOrCreate([ - 'id' => 3, - 'provider' => 'gitlab', - ]); - OauthSetting::firstOrCreate([ - 'id' => 4, - 'provider' => 'google', + $providers = collect([ + 'azure', + 'bitbucket', + 'github', + 'gitlab', + 'google', + 'authentik', ]); + + $isOauthSeeded = OauthSetting::count() > 0; + + // We changed how providers are defined in the database, so we authentik does not exists, we need to recreate all of the auth providers + // Before authentik was a provider, providers started with 0 id + + $isOauthAuthentik = OauthSetting::where('provider', 'authentik')->exists(); + if ($isOauthSeeded) { + if (! $isOauthAuthentik) { + $allProviders = OauthSetting::all(); + $notFoundProviders = $providers->diff($allProviders->pluck('provider')); + + $allProviders->each(function ($provider) { + $provider->delete(); + }); + $allProviders->each(function ($provider) use ($providers) { + $providerName = $provider->provider; + + $foundProvider = $providers->first(function ($provider) use ($providerName) { + return $provider === $providerName; + }); + + if ($foundProvider) { + $newProvder = new OauthSetting; + $newProvder = $provider; + unset($newProvder->id); + $newProvder->save(); + } + }); + + foreach ($notFoundProviders as $provider) { + OauthSetting::create([ + 'provider' => $provider, + ]); + } + } else { + foreach ($providers as $provider) { + OauthSetting::updateOrCreate([ + 'provider' => $provider, + ]); + } + } + } else { + foreach ($providers as $provider) { + OauthSetting::updateOrCreate([ + 'provider' => $provider, + ]); + } + } } } diff --git a/database/seeders/ProductionSeeder.php b/database/seeders/ProductionSeeder.php index 7acdef5489..9a301aa672 100644 --- a/database/seeders/ProductionSeeder.php +++ b/database/seeders/ProductionSeeder.php @@ -22,9 +22,9 @@ class ProductionSeeder extends Seeder public function run(): void { if (isCloud()) { - echo "[x]: Running in cloud mode.\n"; + echo " Running in cloud mode.\n"; } else { - echo "[x]: Running in self-hosted mode.\n"; + echo " Running in self-hosted mode.\n"; } // Fix for 4.0.0-beta.37 diff --git a/database/seeders/TestTeamSeeder.php b/database/seeders/TestTeamSeeder.php deleted file mode 100644 index 940c45cc5a..0000000000 --- a/database/seeders/TestTeamSeeder.php +++ /dev/null @@ -1,42 +0,0 @@ -create([ - 'name' => '1 personal, 1 other team, owner, no other members', - 'email' => '1@example.com', - ]); - $team = Team::create([ - 'name' => '1@example.com', - 'personal_team' => false, - 'show_boarding' => true, - ]); - $user->teams()->attach($team, ['role' => 'owner']); - - // User has 2 teams, 1 personal, 1 other where it is the owner and 1 other member is in the team - $user = User::factory()->create([ - 'name' => 'owner: 1 personal, 1 other team, owner, 1 other member', - 'email' => '2@example.com', - ]); - $team = Team::create([ - 'name' => '2@example.com', - 'personal_team' => false, - 'show_boarding' => true, - ]); - $user->teams()->attach($team, ['role' => 'owner']); - $user = User::factory()->create([ - 'name' => 'member: 1 personal, 1 other team, owner, 1 other member', - 'email' => '3@example.com', - ]); - $team->members()->attach($user, ['role' => 'member']); - } -} diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 05b9f9cfb9..76f8e9ca64 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -2,15 +2,14 @@ services: coolify: build: context: . - dockerfile: ./docker/dev/Dockerfile + dockerfile: ./docker/development/Dockerfile + args: + - USER_ID=${USERID:-1000} + - GROUP_ID=${GROUPID:-1000} ports: - "${APP_PORT:-8000}:80" environment: - PUID: "${USERID:-1000}" - PGID: "${GROUPID:-1000}" - SSL_MODE: "off" - AUTORUN_LARAVEL_STORAGE_LINK: "false" - AUTORUN_LARAVEL_MIGRATION: "false" + AUTORUN_ENABLED: false PUSHER_HOST: "${PUSHER_HOST}" PUSHER_PORT: "${PUSHER_PORT}" PUSHER_SCHEME: "${PUSHER_SCHEME:-http}" diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile deleted file mode 100644 index e905217598..0000000000 --- a/docker/dev/Dockerfile +++ /dev/null @@ -1,61 +0,0 @@ -# Versions -# https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx-alpine -ARG SERVERSIDEUP_PHP_VERSION=8.2-fpm-nginx-v2.2.1 -# https://github.com/minio/mc/releases -ARG MINIO_VERSION=RELEASE.2024-11-05T11-29-45Z -# https://github.com/cloudflare/cloudflared/releases -ARG CLOUDFLARED_VERSION=2024.11.0 -# https://www.postgresql.org/support/versioning/ - Can not updated automatically so keep it at 15 -ARG POSTGRES_VERSION=15 - -FROM minio/mc:${MINIO_VERSION} AS minio-client - -FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} - -ARG TARGETPLATFORM -ARG CLOUDFLARED_VERSION -ARG MINIO_VERSION -ARG POSTGRES_VERSION - -# Use build arguments for caching -ARG BUILDTIME_DEPS="dirmngr ca-certificates software-properties-common gnupg gnupg2 apt-transport-https curl" -ARG RUNTIME_DEPS="postgresql-client-$POSTGRES_VERSION php8.2-pgsql openssh-client git git-lfs jq lsof" - -# Install dependencies -RUN --mount=type=cache,target=/var/cache/apt \ - apt-get update && \ - apt-get install -y $BUILDTIME_DEPS && \ - curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null && \ - echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main | tee -a /etc/apt/sources.list.d/postgresql.list && \ - apt-get update && \ - apt-get install -y $RUNTIME_DEPS && \ - apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* - -COPY --chmod=755 docker/dev/etc/s6-overlay/ /etc/s6-overlay/ - -COPY docker/dev/nginx.conf /etc/nginx/conf.d/custom.conf - -RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc && \ - echo "alias a='php artisan'" >>/etc/bash.bashrc - -RUN mkdir -p /usr/local/bin - -RUN --mount=type=cache,target=/root/.cache \ - /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \ - echo 'amd64' && \ - curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \ - ;fi" - -RUN --mount=type=cache,target=/root/.cache \ - /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \ - echo 'arm64' && \ - curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \ - ;fi" - -COPY --from=minio-client /usr/bin/mc /usr/bin/mc -RUN chmod +x /usr/bin/mc - -RUN { \ - echo 'upload_max_filesize=256M'; \ - echo 'post_max_size=256M'; \ - } > /etc/php/current_version/cli/conf.d/upload-limits.ini diff --git a/docker/dev/etc/s6-overlay/s6-rc.d/horizon/run b/docker/dev/etc/s6-overlay/s6-rc.d/horizon/run deleted file mode 100644 index 87471097e2..0000000000 --- a/docker/dev/etc/s6-overlay/s6-rc.d/horizon/run +++ /dev/null @@ -1,5 +0,0 @@ -#!/command/execlineb -P -foreground { - s6-sleep 5 - su - webuser -c "php /var/www/html/artisan start:horizon" -} diff --git a/docker/dev/etc/s6-overlay/s6-rc.d/init-setup/up b/docker/dev/etc/s6-overlay/s6-rc.d/init-setup/up deleted file mode 100644 index e02307e499..0000000000 --- a/docker/dev/etc/s6-overlay/s6-rc.d/init-setup/up +++ /dev/null @@ -1,5 +0,0 @@ -#!/command/execlineb -P -foreground { composer -d /var/www/html/ install } -foreground { php /var/www/html/artisan migrate --step } -foreground { php /var/www/html/artisan dev --init } - diff --git a/docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/run b/docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/run deleted file mode 100644 index 87ca0cae1b..0000000000 --- a/docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/run +++ /dev/null @@ -1,5 +0,0 @@ -#!/command/execlineb -P -foreground { - s6-sleep 5 - su - webuser -c "php /var/www/html/artisan start:scheduler" -} diff --git a/docker/development/Dockerfile b/docker/development/Dockerfile new file mode 100644 index 0000000000..7d78e2854e --- /dev/null +++ b/docker/development/Dockerfile @@ -0,0 +1,81 @@ +# Versions +# https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine +ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine +# https://github.com/minio/mc/releases +ARG MINIO_VERSION=RELEASE.2024-11-17T19-35-25Z +# https://github.com/cloudflare/cloudflared/releases +ARG CLOUDFLARED_VERSION=2024.11.1 +# https://www.postgresql.org/support/versioning/ +ARG POSTGRES_VERSION=15 + +# ================================================================= +# Get MinIO client +# ================================================================= +FROM minio/mc:${MINIO_VERSION} AS minio-client + +# ================================================================= +# Final Stage: Production image +# ================================================================= +FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} + +ARG USER_ID +ARG GROUP_ID +ARG TARGETPLATFORM +ARG POSTGRES_VERSION +ARG CLOUDFLARED_VERSION + +WORKDIR /var/www/html + +USER root + +RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ + docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx + +# Install PostgreSQL repository and keys +RUN apk add --no-cache gnupg && \ + mkdir -p /usr/share/keyrings && \ + curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor > /usr/share/keyrings/postgresql.gpg + +# Install system dependencies +RUN apk add --no-cache \ + postgresql${POSTGRES_VERSION}-client \ + openssh-client \ + git \ + git-lfs \ + jq \ + lsof \ + vim + +# Configure shell aliases +RUN echo "alias ll='ls -al'" >> /etc/profile && \ + echo "alias a='php artisan'" >> /etc/profile && \ + echo "alias logs='tail -f storage/logs/laravel.log'" >> /etc/profile + +# Install Cloudflared based on architecture +RUN mkdir -p /usr/local/bin && \ + if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then \ + curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64" -o /usr/local/bin/cloudflared; \ + elif [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \ + curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64" -o /usr/local/bin/cloudflared; \ + fi && \ + chmod +x /usr/local/bin/cloudflared + +# Configure PHP +COPY docker/development/etc/php/conf.d/zzz-custom-php.ini /usr/local/etc/php/conf.d/zzz-custom-php.ini +ENV PHP_OPCACHE_ENABLE=0 + +# Configure Nginx and S6 overlay +COPY docker/development/etc/nginx/conf.d/custom.conf /etc/nginx/conf.d/custom.conf +COPY docker/development/etc/nginx/site-opts.d/http.conf /etc/nginx/site-opts.d/http.conf +COPY --chmod=755 docker/development/etc/s6-overlay/ /etc/s6-overlay/ + +RUN mkdir -p /etc/nginx/conf.d && \ + chown -R www-data:www-data /etc/nginx && \ + chmod -R 755 /etc/nginx + +# Install MinIO client +COPY --from=minio-client /usr/bin/mc /usr/bin/mc +RUN chmod +x /usr/bin/mc + +# Switch to non-root user +USER www-data diff --git a/docker/dev/nginx.conf b/docker/development/etc/nginx/conf.d/custom.conf similarity index 100% rename from docker/dev/nginx.conf rename to docker/development/etc/nginx/conf.d/custom.conf diff --git a/docker/development/etc/nginx/site-opts.d/http.conf b/docker/development/etc/nginx/site-opts.d/http.conf new file mode 100644 index 0000000000..41735cf06c --- /dev/null +++ b/docker/development/etc/nginx/site-opts.d/http.conf @@ -0,0 +1,45 @@ +listen 80 default_server; +listen [::]:80 default_server; +listen 8080 default_server; +listen [::]:8080 default_server; + +root /var/www/html/public; + +# Set allowed "index" files +index index.html index.htm index.php; + +server_name _; + +charset utf-8; + +# Set max upload to 2048M +client_max_body_size 2048M; + +# Healthchecks: Set /healthcheck to be the healthcheck URL +location /healthcheck { + access_log off; + + # set max 5 seconds for healthcheck + fastcgi_read_timeout 5s; + + include fastcgi_params; + fastcgi_param SCRIPT_NAME /healthcheck; + fastcgi_param SCRIPT_FILENAME /healthcheck; + fastcgi_pass 127.0.0.1:9000; +} + +# Have NGINX try searching for PHP files as well +location / { + try_files $uri $uri/ /index.php?$query_string; +} + +# Pass "*.php" files to PHP-FPM +location ~ \.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + fastcgi_buffers 8 8k; + fastcgi_buffer_size 8k; + fastcgi_read_timeout 99; +} diff --git a/docker/development/etc/php/conf.d/zzz-custom-php.ini b/docker/development/etc/php/conf.d/zzz-custom-php.ini new file mode 100644 index 0000000000..dcf0489045 --- /dev/null +++ b/docker/development/etc/php/conf.d/zzz-custom-php.ini @@ -0,0 +1,10 @@ +error_reporting = E_ERROR +error_log = /dev/stderr +log_errors = On +log_errors_max_len = 8192 +ignore_repeated_errors = On +ignore_repeated_source = On + +upload_max_filesize = 256M +post_max_size = 256M +memory_limit = ${PHP_MEMORY_LIMIT:-256M} diff --git a/docker/dev/etc/s6-overlay/s6-rc.d/horizon/dependencies.d/init-setup b/docker/development/etc/s6-overlay/s6-rc.d/horizon/dependencies.d/init-setup similarity index 100% rename from docker/dev/etc/s6-overlay/s6-rc.d/horizon/dependencies.d/init-setup rename to docker/development/etc/s6-overlay/s6-rc.d/horizon/dependencies.d/init-setup diff --git a/docker/development/etc/s6-overlay/s6-rc.d/horizon/run b/docker/development/etc/s6-overlay/s6-rc.d/horizon/run new file mode 100644 index 0000000000..ada19b3a30 --- /dev/null +++ b/docker/development/etc/s6-overlay/s6-rc.d/horizon/run @@ -0,0 +1,12 @@ +#!/command/execlineb -P + +# Use with-contenv to ensure environment variables are available +with-contenv +cd /var/www/html + +foreground { + php + artisan + start:horizon +} + diff --git a/docker/dev/etc/s6-overlay/s6-rc.d/horizon/type b/docker/development/etc/s6-overlay/s6-rc.d/horizon/type similarity index 100% rename from docker/dev/etc/s6-overlay/s6-rc.d/horizon/type rename to docker/development/etc/s6-overlay/s6-rc.d/horizon/type diff --git a/docker/dev/etc/s6-overlay/s6-rc.d/init-setup/type b/docker/development/etc/s6-overlay/s6-rc.d/init-setup/type similarity index 100% rename from docker/dev/etc/s6-overlay/s6-rc.d/init-setup/type rename to docker/development/etc/s6-overlay/s6-rc.d/init-setup/type diff --git a/docker/development/etc/s6-overlay/s6-rc.d/init-setup/up b/docker/development/etc/s6-overlay/s6-rc.d/init-setup/up new file mode 100644 index 0000000000..67e0f5c1a7 --- /dev/null +++ b/docker/development/etc/s6-overlay/s6-rc.d/init-setup/up @@ -0,0 +1,22 @@ +#!/command/execlineb -P + +# Use with-contenv to ensure environment variables are available +with-contenv +cd /var/www/html +foreground { + composer + install +} +foreground { + php + artisan + migrate + --step +} +foreground { + php + artisan + dev + --init +} + diff --git a/docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/dependencies.d/init-setup b/docker/development/etc/s6-overlay/s6-rc.d/scheduler-worker/dependencies.d/init-setup similarity index 100% rename from docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/dependencies.d/init-setup rename to docker/development/etc/s6-overlay/s6-rc.d/scheduler-worker/dependencies.d/init-setup diff --git a/docker/development/etc/s6-overlay/s6-rc.d/scheduler-worker/run b/docker/development/etc/s6-overlay/s6-rc.d/scheduler-worker/run new file mode 100644 index 0000000000..b81a448338 --- /dev/null +++ b/docker/development/etc/s6-overlay/s6-rc.d/scheduler-worker/run @@ -0,0 +1,13 @@ +#!/command/execlineb -P + +# Use with-contenv to ensure environment variables are available +with-contenv +cd /var/www/html + +foreground { + php + artisan + start:scheduler +} + + diff --git a/docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/type b/docker/development/etc/s6-overlay/s6-rc.d/scheduler-worker/type similarity index 100% rename from docker/dev/etc/s6-overlay/s6-rc.d/scheduler-worker/type rename to docker/development/etc/s6-overlay/s6-rc.d/scheduler-worker/type diff --git a/docker/dev/etc/s6-overlay/s6-rc.d/user/contents.d/horizon b/docker/development/etc/s6-overlay/s6-rc.d/user/contents.d/horizon similarity index 100% rename from docker/dev/etc/s6-overlay/s6-rc.d/user/contents.d/horizon rename to docker/development/etc/s6-overlay/s6-rc.d/user/contents.d/horizon diff --git a/docker/dev/etc/s6-overlay/s6-rc.d/user/contents.d/init-setup b/docker/development/etc/s6-overlay/s6-rc.d/user/contents.d/init-setup similarity index 100% rename from docker/dev/etc/s6-overlay/s6-rc.d/user/contents.d/init-setup rename to docker/development/etc/s6-overlay/s6-rc.d/user/contents.d/init-setup diff --git a/docker/dev/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker b/docker/development/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker similarity index 100% rename from docker/dev/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker rename to docker/development/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile deleted file mode 100644 index 36be6cf067..0000000000 --- a/docker/prod/Dockerfile +++ /dev/null @@ -1,82 +0,0 @@ -# Versions -# https://hub.docker.com/r/serversideup/php/tags?name=8.3-fpm-nginx-alpine -ARG SERVERSIDEUP_PHP_VERSION=8.2-fpm-nginx-v2.2.1 -# https://github.com/minio/mc/releases -ARG MINIO_VERSION=RELEASE.2024-11-05T11-29-45Z -# https://github.com/cloudflare/cloudflared/releases -ARG CLOUDFLARED_VERSION=2024.11.0 -# https://www.postgresql.org/support/versioning/ - Can not updated automatically so keep it at 15 -ARG POSTGRES_VERSION=15 - - -FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} AS base -WORKDIR /var/www/html - -COPY composer.json composer.lock ./ -RUN composer install --no-dev --no-interaction --no-plugins --no-scripts --prefer-dist - -FROM node:20 AS static-assets -WORKDIR /app -COPY . . -COPY --from=base --chown=9999:9999 /var/www/html . -RUN npm install -RUN npm run build - -FROM minio/mc:${MINIO_VERSION} AS minio-client - -FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} - -ARG TARGETPLATFORM -ARG CLOUDFLARED_VERSION -ARG POSTGRES_VERSION -ARG CI=true - -WORKDIR /var/www/html - -RUN apt-get update -# Postgres version requirements -RUN apt install dirmngr ca-certificates software-properties-common gnupg gnupg2 apt-transport-https curl -y -RUN curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/postgresql.gpg > /dev/null - -RUN echo deb [arch=amd64,arm64,ppc64el signed-by=/usr/share/keyrings/postgresql.gpg] http://apt.postgresql.org/pub/repos/apt/ jammy-pgdg main | tee -a /etc/apt/sources.list.d/postgresql.list - -RUN apt-get update -RUN apt-get install postgresql-client-${POSTGRES_VERSION} -y - -# Coolify requirements -RUN apt-get install -y php8.2-pgsql openssh-client git git-lfs jq lsof vim -RUN apt-get -y autoremove && apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/* - -COPY docker/prod/nginx.conf /etc/nginx/conf.d/custom.conf - -COPY --from=base --chown=9999:9999 /var/www/html . - -COPY --chown=9999:9999 . . -RUN composer dump-autoload - -COPY --from=static-assets --chown=9999:9999 /app/public/build ./public/build -COPY --chmod=755 docker/prod/etc/s6-overlay/ /etc/s6-overlay/ - -RUN echo "alias ll='ls -al'" >>/etc/bash.bashrc -RUN echo "alias a='php artisan'" >>/etc/bash.bashrc -RUN echo "alias logs='tail -f storage/logs/laravel.log'" >>/etc/bash.bashrc - -RUN mkdir -p /usr/local/bin - -RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/amd64' ]]; then \ - echo 'amd64' && \ - curl -sSL https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \ - ;fi" - -RUN /bin/bash -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \ - echo 'arm64' && \ - curl -L https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared \ - ;fi" - -RUN { \ - echo 'upload_max_filesize=256M'; \ - echo 'post_max_size=256M'; \ - } > /etc/php/current_version/cli/conf.d/upload-limits.ini - -COPY --from=minio-client /usr/bin/mc /usr/bin/mc -RUN chmod +x /usr/bin/mc diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/db-migration/up b/docker/prod/etc/s6-overlay/s6-rc.d/db-migration/up deleted file mode 100644 index 250d5d8b14..0000000000 --- a/docker/prod/etc/s6-overlay/s6-rc.d/db-migration/up +++ /dev/null @@ -1,2 +0,0 @@ -#!/command/execlineb -P -php /var/www/html/artisan migrate --force --isolated diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/horizon/run b/docker/prod/etc/s6-overlay/s6-rc.d/horizon/run deleted file mode 100644 index 87471097e2..0000000000 --- a/docker/prod/etc/s6-overlay/s6-rc.d/horizon/run +++ /dev/null @@ -1,5 +0,0 @@ -#!/command/execlineb -P -foreground { - s6-sleep 5 - su - webuser -c "php /var/www/html/artisan start:horizon" -} diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/init-script/up b/docker/prod/etc/s6-overlay/s6-rc.d/init-script/up deleted file mode 100644 index 3b252b7829..0000000000 --- a/docker/prod/etc/s6-overlay/s6-rc.d/init-script/up +++ /dev/null @@ -1,3 +0,0 @@ -#!/command/execlineb -P -s6-setuidgid webuser -php /var/www/html/artisan app:init diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/init-seeder/up b/docker/prod/etc/s6-overlay/s6-rc.d/init-seeder/up deleted file mode 100644 index 44645c11fe..0000000000 --- a/docker/prod/etc/s6-overlay/s6-rc.d/init-seeder/up +++ /dev/null @@ -1,2 +0,0 @@ -#!/command/execlineb -P -php /var/www/html/artisan db:seed --class ProductionSeeder --force diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/scheduler-worker/run b/docker/prod/etc/s6-overlay/s6-rc.d/scheduler-worker/run deleted file mode 100644 index 87ca0cae1b..0000000000 --- a/docker/prod/etc/s6-overlay/s6-rc.d/scheduler-worker/run +++ /dev/null @@ -1,5 +0,0 @@ -#!/command/execlineb -P -foreground { - s6-sleep 5 - su - webuser -c "php /var/www/html/artisan start:scheduler" -} diff --git a/docker/production/Dockerfile b/docker/production/Dockerfile new file mode 100644 index 0000000000..09281a666e --- /dev/null +++ b/docker/production/Dockerfile @@ -0,0 +1,136 @@ +# Versions +# https://hub.docker.com/r/serversideup/php/tags?name=8.4-fpm-nginx-alpine +ARG SERVERSIDEUP_PHP_VERSION=8.4-fpm-nginx-alpine +# https://github.com/minio/mc/releases +ARG MINIO_VERSION=RELEASE.2024-11-17T19-35-25Z +# https://github.com/cloudflare/cloudflared/releases +ARG CLOUDFLARED_VERSION=2024.11.1 +# https://www.postgresql.org/support/versioning/ +ARG POSTGRES_VERSION=15 + +# Add user/group +ARG USER_ID=9999 +ARG GROUP_ID=9999 + +# ================================================================= +# Stage 1: Composer dependencies +# ================================================================= +FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} AS base + +USER root + +ARG USER_ID +ARG GROUP_ID + +RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ + docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx + +WORKDIR /var/www/html +COPY --chown=www-data:www-data composer.json composer.lock ./ +RUN composer install --no-dev --no-interaction --no-plugins --no-scripts --prefer-dist + +USER www-data + +# ================================================================= +# Stage 2: Frontend assets compilation +# ================================================================= +FROM node:20-alpine AS static-assets + +WORKDIR /app +COPY package*.json vite.config.js tailwind.config.js postcss.config.cjs ./ +RUN npm ci +COPY . . +RUN npm run build + +# ================================================================= +# Stage 3: Get MinIO client +# ================================================================= +FROM minio/mc:${MINIO_VERSION} AS minio-client + +# ================================================================= +# Final Stage: Production image +# ================================================================= +FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} + +ARG USER_ID +ARG GROUP_ID +ARG TARGETPLATFORM +ARG POSTGRES_VERSION +ARG CLOUDFLARED_VERSION +ARG CI=true + +WORKDIR /var/www/html + +USER root + +RUN docker-php-serversideup-set-id www-data $USER_ID:$GROUP_ID && \ + docker-php-serversideup-set-file-permissions --owner $USER_ID:$GROUP_ID --service nginx + +# Install PostgreSQL repository and keys +RUN apk add --no-cache gnupg && \ + mkdir -p /usr/share/keyrings && \ + curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor > /usr/share/keyrings/postgresql.gpg + +# Install system dependencies +RUN apk add --no-cache \ + postgresql${POSTGRES_VERSION}-client \ + openssh-client \ + git \ + git-lfs \ + jq \ + lsof \ + vim + +# Configure shell aliases +RUN echo "alias ll='ls -al'" >> /etc/profile && \ + echo "alias a='php artisan'" >> /etc/profile && \ + echo "alias logs='tail -f storage/logs/laravel.log'" >> /etc/profile + +# Install Cloudflared based on architecture +RUN mkdir -p /usr/local/bin && \ + if [ "${TARGETPLATFORM}" = "linux/amd64" ]; then \ + curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-amd64" -o /usr/local/bin/cloudflared; \ + elif [ "${TARGETPLATFORM}" = "linux/arm64" ]; then \ + curl -sSL "https://github.com/cloudflare/cloudflared/releases/download/${CLOUDFLARED_VERSION}/cloudflared-linux-arm64" -o /usr/local/bin/cloudflared; \ + fi && \ + chmod +x /usr/local/bin/cloudflared + +# Configure PHP +COPY docker/production/etc/php/conf.d/zzz-custom-php.ini /usr/local/etc/php/conf.d/zzz-custom-php.ini +ENV PHP_OPCACHE_ENABLE=1 + +# Copy application files from previous stages +COPY --from=base --chown=www-data:www-data /var/www/html/vendor ./vendor +COPY --from=static-assets --chown=www-data:www-data /app/public/build ./public/build + +# Copy application source code +COPY --chown=www-data:www-data composer.json composer.lock ./ +COPY --chown=www-data:www-data app ./app +COPY --chown=www-data:www-data bootstrap ./bootstrap +COPY --chown=www-data:www-data config ./config +COPY --chown=www-data:www-data database ./database +COPY --chown=www-data:www-data lang ./lang +COPY --chown=www-data:www-data public ./public +COPY --chown=www-data:www-data routes ./routes +COPY --chown=www-data:www-data storage ./storage +COPY --chown=www-data:www-data templates ./templates +COPY --chown=www-data:www-data resources/views ./resources/views +COPY --chown=www-data:www-data artisan artisan + +RUN composer dump-autoload + +# Configure Nginx and S6 overlay +COPY docker/production/etc/nginx/conf.d/custom.conf /etc/nginx/conf.d/custom.conf +COPY docker/production/etc/nginx/site-opts.d/http.conf /etc/nginx/site-opts.d/http.conf +COPY --chmod=755 docker/production/etc/s6-overlay/ /etc/s6-overlay/ + +RUN mkdir -p /etc/nginx/conf.d && \ + chown -R www-data:www-data /etc/nginx && \ + chmod -R 755 /etc/nginx + +# Install MinIO client +COPY --from=minio-client /usr/bin/mc /usr/bin/mc +RUN chmod +x /usr/bin/mc + +# Switch to non-root user +USER www-data diff --git a/docker/prod/nginx.conf b/docker/production/etc/nginx/conf.d/custom.conf similarity index 100% rename from docker/prod/nginx.conf rename to docker/production/etc/nginx/conf.d/custom.conf diff --git a/docker/production/etc/nginx/site-opts.d/http.conf b/docker/production/etc/nginx/site-opts.d/http.conf new file mode 100644 index 0000000000..41735cf06c --- /dev/null +++ b/docker/production/etc/nginx/site-opts.d/http.conf @@ -0,0 +1,45 @@ +listen 80 default_server; +listen [::]:80 default_server; +listen 8080 default_server; +listen [::]:8080 default_server; + +root /var/www/html/public; + +# Set allowed "index" files +index index.html index.htm index.php; + +server_name _; + +charset utf-8; + +# Set max upload to 2048M +client_max_body_size 2048M; + +# Healthchecks: Set /healthcheck to be the healthcheck URL +location /healthcheck { + access_log off; + + # set max 5 seconds for healthcheck + fastcgi_read_timeout 5s; + + include fastcgi_params; + fastcgi_param SCRIPT_NAME /healthcheck; + fastcgi_param SCRIPT_FILENAME /healthcheck; + fastcgi_pass 127.0.0.1:9000; +} + +# Have NGINX try searching for PHP files as well +location / { + try_files $uri $uri/ /index.php?$query_string; +} + +# Pass "*.php" files to PHP-FPM +location ~ \.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_index index.php; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + include fastcgi_params; + fastcgi_buffers 8 8k; + fastcgi_buffer_size 8k; + fastcgi_read_timeout 99; +} diff --git a/docker/production/etc/php/conf.d/zzz-custom-php.ini b/docker/production/etc/php/conf.d/zzz-custom-php.ini new file mode 100644 index 0000000000..ee18b77e95 --- /dev/null +++ b/docker/production/etc/php/conf.d/zzz-custom-php.ini @@ -0,0 +1,10 @@ +error_reporting = E_ERROR +error_log = /var/www/html/storage/logs/php-error.log +log_errors = Off +log_errors_max_len = 8192 +ignore_repeated_errors = On +ignore_repeated_source = On + +upload_max_filesize = 256M +post_max_size = 256M +memory_limit = ${PHP_MEMORY_LIMIT:-256M} diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/db-migration/type b/docker/production/etc/s6-overlay/s6-rc.d/db-migration/type similarity index 100% rename from docker/prod/etc/s6-overlay/s6-rc.d/db-migration/type rename to docker/production/etc/s6-overlay/s6-rc.d/db-migration/type diff --git a/docker/production/etc/s6-overlay/s6-rc.d/db-migration/up b/docker/production/etc/s6-overlay/s6-rc.d/db-migration/up new file mode 100644 index 0000000000..c2548d1360 --- /dev/null +++ b/docker/production/etc/s6-overlay/s6-rc.d/db-migration/up @@ -0,0 +1,11 @@ +#!/command/execlineb -P + +# Use with-contenv to ensure environment variables are available +with-contenv +cd /var/www/html +foreground { + php + artisan + start:migration +} + diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/init-script b/docker/production/etc/s6-overlay/s6-rc.d/horizon/dependencies.d/init-script similarity index 100% rename from docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/init-script rename to docker/production/etc/s6-overlay/s6-rc.d/horizon/dependencies.d/init-script diff --git a/docker/production/etc/s6-overlay/s6-rc.d/horizon/run b/docker/production/etc/s6-overlay/s6-rc.d/horizon/run new file mode 100644 index 0000000000..be66476079 --- /dev/null +++ b/docker/production/etc/s6-overlay/s6-rc.d/horizon/run @@ -0,0 +1,11 @@ +#!/command/execlineb -P + +# Use with-contenv to ensure environment variables are available +with-contenv +cd /var/www/html +foreground { + php + artisan + start:horizon +} + diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/horizon/type b/docker/production/etc/s6-overlay/s6-rc.d/horizon/type similarity index 100% rename from docker/prod/etc/s6-overlay/s6-rc.d/horizon/type rename to docker/production/etc/s6-overlay/s6-rc.d/horizon/type diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/init-script/dependencies.d/init-seeder b/docker/production/etc/s6-overlay/s6-rc.d/init-script/dependencies.d/init-seeder similarity index 100% rename from docker/prod/etc/s6-overlay/s6-rc.d/init-script/dependencies.d/init-seeder rename to docker/production/etc/s6-overlay/s6-rc.d/init-script/dependencies.d/init-seeder diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/init-script/type b/docker/production/etc/s6-overlay/s6-rc.d/init-script/type similarity index 100% rename from docker/prod/etc/s6-overlay/s6-rc.d/init-script/type rename to docker/production/etc/s6-overlay/s6-rc.d/init-script/type diff --git a/docker/production/etc/s6-overlay/s6-rc.d/init-script/up b/docker/production/etc/s6-overlay/s6-rc.d/init-script/up new file mode 100644 index 0000000000..3fad5f2c5f --- /dev/null +++ b/docker/production/etc/s6-overlay/s6-rc.d/init-script/up @@ -0,0 +1,12 @@ +#!/command/execlineb -P + +# Use with-contenv to ensure environment variables are available +with-contenv +cd /var/www/html +foreground { + php + artisan + app:init +} + + diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/init-seeder/dependencies.d/db-migration b/docker/production/etc/s6-overlay/s6-rc.d/init-seeder/dependencies.d/db-migration similarity index 100% rename from docker/prod/etc/s6-overlay/s6-rc.d/init-seeder/dependencies.d/db-migration rename to docker/production/etc/s6-overlay/s6-rc.d/init-seeder/dependencies.d/db-migration diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/init-seeder/type b/docker/production/etc/s6-overlay/s6-rc.d/init-seeder/type similarity index 100% rename from docker/prod/etc/s6-overlay/s6-rc.d/init-seeder/type rename to docker/production/etc/s6-overlay/s6-rc.d/init-seeder/type diff --git a/docker/production/etc/s6-overlay/s6-rc.d/init-seeder/up b/docker/production/etc/s6-overlay/s6-rc.d/init-seeder/up new file mode 100644 index 0000000000..007f4233e5 --- /dev/null +++ b/docker/production/etc/s6-overlay/s6-rc.d/init-seeder/up @@ -0,0 +1,12 @@ +#!/command/execlineb -P + +# Use with-contenv to ensure environment variables are available +with-contenv +cd /var/www/html +foreground { + php + artisan + start:seeder +} + + diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/db-migration b/docker/production/etc/s6-overlay/s6-rc.d/scheduler-worker/dependencies.d/init-script similarity index 100% rename from docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/db-migration rename to docker/production/etc/s6-overlay/s6-rc.d/scheduler-worker/dependencies.d/init-script diff --git a/docker/production/etc/s6-overlay/s6-rc.d/scheduler-worker/run b/docker/production/etc/s6-overlay/s6-rc.d/scheduler-worker/run new file mode 100644 index 0000000000..a2ecb0a73c --- /dev/null +++ b/docker/production/etc/s6-overlay/s6-rc.d/scheduler-worker/run @@ -0,0 +1,10 @@ +#!/command/execlineb -P + +# Use with-contenv to ensure environment variables are available +with-contenv +cd /var/www/html +foreground { + php + artisan + start:scheduler +} diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/scheduler-worker/type b/docker/production/etc/s6-overlay/s6-rc.d/scheduler-worker/type similarity index 100% rename from docker/prod/etc/s6-overlay/s6-rc.d/scheduler-worker/type rename to docker/production/etc/s6-overlay/s6-rc.d/scheduler-worker/type diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/horizon b/docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/db-migration similarity index 100% rename from docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/horizon rename to docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/db-migration diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/init-seeder b/docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/horizon similarity index 100% rename from docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/init-seeder rename to docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/horizon diff --git a/docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker b/docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/init-script similarity index 100% rename from docker/prod/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker rename to docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/init-script diff --git a/docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/init-seeder b/docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/init-seeder new file mode 100644 index 0000000000..e69de29bb2 diff --git a/docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker b/docker/production/etc/s6-overlay/s6-rc.d/user/contents.d/scheduler-worker new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lang/en.json b/lang/en.json index 5ea474b028..4e0749ece8 100644 --- a/lang/en.json +++ b/lang/en.json @@ -1,5 +1,6 @@ { "auth.login": "Login", + "auth.login.authentik": "Login with Authentik", "auth.login.azure": "Login with Microsoft", "auth.login.bitbucket": "Login with Bitbucket", "auth.login.github": "Login with GitHub", diff --git a/openapi.json b/openapi.json index 2ec218438d..5d35331ec6 100644 --- a/openapi.json +++ b/openapi.json @@ -3011,7 +3011,7 @@ "type": "string", "description": "Mongo initdb root password" }, - "mongo_initdb_init_database": { + "mongo_initdb_database": { "type": "string", "description": "Mongo initdb init database" }, @@ -3019,6 +3019,10 @@ "type": "string", "description": "MySQL root password" }, + "mysql_password": { + "type": "string", + "description": "MySQL password" + }, "mysql_user": { "type": "string", "description": "MySQL user" @@ -3842,6 +3846,10 @@ "type": "string", "description": "MySQL root password" }, + "mysql_password": { + "type": "string", + "description": "MySQL password" + }, "mysql_user": { "type": "string", "description": "MySQL user" diff --git a/openapi.yaml b/openapi.yaml index 2a22c730c5..20bf348730 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -2089,12 +2089,15 @@ paths: mongo_initdb_root_password: type: string description: 'Mongo initdb root password' - mongo_initdb_init_database: + mongo_initdb_database: type: string description: 'Mongo initdb init database' mysql_root_password: type: string description: 'MySQL root password' + mysql_password: + type: string + description: 'MySQL password' mysql_user: type: string description: 'MySQL user' @@ -2684,6 +2687,9 @@ paths: mysql_root_password: type: string description: 'MySQL root password' + mysql_password: + type: string + description: 'MySQL password' mysql_user: type: string description: 'MySQL user' diff --git a/other/logos/internetgarden.ico b/other/logos/internetgarden.ico new file mode 100644 index 0000000000..e555984be3 Binary files /dev/null and b/other/logos/internetgarden.ico differ diff --git a/other/nightly/install.sh b/other/nightly/install.sh index 4a03a5c98a..def105aa72 100755 --- a/other/nightly/install.sh +++ b/other/nightly/install.sh @@ -13,6 +13,50 @@ DOCKER_VERSION="27.0" # TODO: Ask for a user CURRENT_USER=$USER +if [ $EUID != 0 ]; then + echo "Please run this script as root or with sudo" + exit +fi + +echo -e "Welcome to Coolify Installer!" +echo -e "This script will install everything for you. Sit back and relax." +echo -e "Source code: https://github.com/coollabsio/coolify/blob/main/scripts/install.sh\n" + +TOTAL_SPACE=$(df -BG / | awk 'NR==2 {print $2}' | sed 's/G//') +AVAILABLE_SPACE=$(df -BG / | awk 'NR==2 {print $4}' | sed 's/G//') +REQUIRED_TOTAL_SPACE=30 +REQUIRED_AVAILABLE_SPACE=20 +WARNING_SPACE=false + +if [ "$TOTAL_SPACE" -lt "$REQUIRED_TOTAL_SPACE" ]; then + WARNING_SPACE=true + cat << EOF +WARNING: Insufficient total disk space! + +Total disk space: ${TOTAL_SPACE}GB +Required disk space: ${REQUIRED_TOTAL_SPACE}GB + +================== +EOF +fi + +if [ "$AVAILABLE_SPACE" -lt "$REQUIRED_AVAILABLE_SPACE" ]; then + cat << EOF +WARNING: Insufficient available disk space! + +Available disk space: ${AVAILABLE_SPACE}GB +Required available space: ${REQUIRED_AVAILABLE_SPACE}GB + +================== +EOF +WARNING_SPACE=true +fi + +if [ "$WARNING_SPACE" = true ]; then + echo "Sleeping for 5 seconds." + sleep 5 +fi + mkdir -p /data/coolify/{source,ssh,applications,databases,backups,services,proxy,webhooks-during-maintenance,sentinel} mkdir -p /data/coolify/ssh/{keys,mux} mkdir -p /data/coolify/proxy/dynamic @@ -39,6 +83,11 @@ if [ "$OS_TYPE" = "manjaro" ] || [ "$OS_TYPE" = "manjaro-arm" ]; then OS_TYPE="arch" fi +# Check if the OS is Endeavour OS, if so, change it to arch +if [ "$OS_TYPE" = "endeavouros" ]; then + OS_TYPE="arch" +fi + # Check if the OS is Asahi Linux, if so, change it to fedora if [ "$OS_TYPE" = "fedora-asahi-remix" ]; then OS_TYPE="fedora" @@ -83,11 +132,6 @@ if [ -z "$LATEST_REALTIME_VERSION" ]; then fi -if [ $EUID != 0 ]; then - echo "Please run as root" - exit -fi - case "$OS_TYPE" in arch | ubuntu | debian | raspbian | centos | fedora | rhel | ol | rocky | sles | opensuse-leap | opensuse-tumbleweed | almalinux | amzn | alpine) ;; *) @@ -103,21 +147,8 @@ if [ "$1" != "" ]; then LATEST_VERSION="${LATEST_VERSION#v}" fi -echo -e "\033[0;35m" -cat << "EOF" - _____ _ _ __ - / ____| | (_)/ _| - | | ___ ___ | |_| |_ _ _ - | | / _ \ / _ \| | | _| | | | - | |___| (_) | (_) | | | | | |_| | - \_____\___/ \___/|_|_|_| \__, | - __/ | - |___/ -EOF -echo -e "\033[0m" -echo -e "Welcome to Coolify Installer!" -echo -e "This script will install everything for you. Sit back and relax." -echo -e "Source code: https://github.com/coollabsio/coolify/blob/main/scripts/install.sh\n" + + echo -e "---------------------------------------------" echo "| Operating System | $OS_TYPE $OS_VERSION" echo "| Docker | $DOCKER_VERSION" @@ -125,24 +156,24 @@ echo "| Coolify | $LATEST_VERSION" echo "| Helper | $LATEST_HELPER_VERSION" echo "| Realtime | $LATEST_REALTIME_VERSION" echo -e "---------------------------------------------\n" -echo -e "1. Installing required packages (curl, wget, git, jq). " +echo -e "1. Installing required packages (curl, wget, git, jq, openssl). " case "$OS_TYPE" in arch) - pacman -Sy --noconfirm --needed curl wget git jq >/dev/null || true + pacman -Sy --noconfirm --needed curl wget git jq openssl >/dev/null || true ;; alpine) sed -i '/^#.*\/community/s/^#//' /etc/apk/repositories apk update >/dev/null - apk add curl wget git jq >/dev/null + apk add curl wget git jq openssl >/dev/null ;; ubuntu | debian | raspbian) apt-get update -y >/dev/null - apt-get install -y curl wget git jq >/dev/null + apt-get install -y curl wget git jq openssl >/dev/null ;; centos | fedora | rhel | ol | rocky | almalinux | amzn) if [ "$OS_TYPE" = "amzn" ]; then - dnf install -y wget git jq >/dev/null + dnf install -y wget git jq openssl >/dev/null else if ! command -v dnf >/dev/null; then yum install -y dnf >/dev/null @@ -150,12 +181,12 @@ centos | fedora | rhel | ol | rocky | almalinux | amzn) if ! command -v curl >/dev/null; then dnf install -y curl >/dev/null fi - dnf install -y wget git jq >/dev/null + dnf install -y wget git jq openssl >/dev/null fi ;; sles | opensuse-leap | opensuse-tumbleweed) zypper refresh >/dev/null - zypper install -y curl wget git jq >/dev/null + zypper install -y curl wget git jq openssl >/dev/null ;; *) echo "This script only supports Debian, Redhat, Arch Linux, or SLES based operating systems for now." @@ -300,6 +331,22 @@ if ! [ -x "$(command -v docker)" ]; then exit 1 fi ;; + "fedora") + if [ -x "$(command -v dnf5)" ]; then + # dnf5 is available + dnf config-manager addrepo --from-repofile=https://download.docker.com/linux/fedora/docker-ce.repo --overwrite >/dev/null 2>&1 + else + # dnf5 is not available, use dnf + dnf config-manager --add-repo=https://download.docker.com/linux/fedora/docker-ce.repo >/dev/null 2>&1 + fi + dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin >/dev/null 2>&1 + if ! [ -x "$(command -v docker)" ]; then + echo " - Docker could not be installed automatically. Please visit https://docs.docker.com/engine/install/ and install Docker manually to continue." + exit 1 + fi + systemctl start docker >/dev/null 2>&1 + systemctl enable docker >/dev/null 2>&1 + ;; *) if [ "$OS_TYPE" = "ubuntu" ] && [ "$OS_VERSION" = "24.10" ]; then echo "Docker automated installation is not supported on Ubuntu 24.10 (non-LTS release)." @@ -490,7 +537,21 @@ echo -e "\033[0;35m \____\___/|_| |_|\__, |_| \__,_|\__|\__,_|_|\__,_|\__|_|\___/|_| |_|___(_) |___/ \033[0m" -echo -e "\nYour instance is ready to use." -echo -e "Please visit http://$(curl -4s https://ifconfig.io):8000 to get started.\n" -echo -e "WARNING: We recommend you to backup your /data/coolify/source/.env file to a safe location, outside of this server." +echo -e "\nYour instance is ready to use!\n" +echo -e "You can access Coolify through your Public IP: http://$(curl -4s https://ifconfig.io):8000" + +set +e +DEFAULT_PRIVATE_IP=$(ip route get 1 | sed -n 's/^.*src \([0-9.]*\) .*$/\1/p') +PRIVATE_IPS=$(hostname -I) +set -e + +if [ -n "$PRIVATE_IPS" ]; then + echo -e "\nIf your Public IP is not accessible, you can use the following Private IPs:\n" + for IP in $PRIVATE_IPS; do + if [ "$IP" != "$DEFAULT_PRIVATE_IP" ]; then + echo -e "http://$IP:8000" + fi + done +fi +echo -e "\nWARNING: It is highly recommended to backup your Environment variables file (/data/coolify/source/.env) to a safe location, outside of this server (e.g. into a Password Manager).\n" cp /data/coolify/source/.env /data/coolify/source/.env.backup diff --git a/other/nightly/versions.json b/other/nightly/versions.json index 8b10875d02..2d1633ed6f 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,16 +1,16 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.367" + "version": "4.0.0-beta.376" }, "nightly": { - "version": "4.0.0-beta.368" + "version": "4.0.0-beta.377" }, "helper": { - "version": "1.0.3" + "version": "1.0.4" }, "realtime": { - "version": "1.0.4" + "version": "1.0.5" }, "sentinel": { "version": "0.0.15" diff --git a/package-lock.json b/package-lock.json index 22398dcf56..e88e191b28 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,21 +10,21 @@ "@tailwindcss/typography": "0.5.15", "@xterm/addon-fit": "^0.10.0", "@xterm/xterm": "^5.5.0", - "alpinejs": "3.14.3", + "alpinejs": "3.14.7", "ioredis": "5.4.1" }, "devDependencies": { - "@vitejs/plugin-vue": "5.2.0", + "@vitejs/plugin-vue": "5.2.1", "autoprefixer": "10.4.20", - "axios": "1.7.7", - "laravel-echo": "1.17.0", - "laravel-vite-plugin": "1.0.6", + "axios": "1.7.9", + "laravel-echo": "1.17.1", + "laravel-vite-plugin": "1.1.1", "postcss": "8.4.49", "pusher-js": "8.4.0-rc2", "tailwind-scrollbar": "^3.1.0", - "tailwindcss": "3.4.14", - "vite": "5.4.11", - "vue": "3.5.12" + "tailwindcss": "3.4.16", + "vite": "6.0.3", + "vue": "3.5.13" } }, "node_modules/@alloc/quick-lru": { @@ -59,13 +59,13 @@ } }, "node_modules/@babel/parser": { - "version": "7.26.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.2.tgz", - "integrity": "sha512-DWMCZH9WA4Maitz2q21SRKHo9QXZxkDsbNZoVD62gusNtNBBqDg9i7uOhASfTfIGNzW+O+r7+jAlM8dwphcJKQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.26.0" + "@babel/types": "^7.26.3" }, "bin": { "parser": "bin/babel-parser.js" @@ -75,9 +75,9 @@ } }, "node_modules/@babel/types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.0.tgz", - "integrity": "sha512-Z/yiTPj+lDVnF7lWeKCIJzaIkI0vYO87dMpZ4bg4TDrFe4XXLFWL1TbXU27gBP3QccxV9mZICCrnjnYlJjXHOA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "dev": true, "license": "MIT", "dependencies": { @@ -89,9 +89,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.0.tgz", + "integrity": "sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==", "cpu": [ "ppc64" ], @@ -102,13 +102,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.24.0.tgz", + "integrity": "sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==", "cpu": [ "arm" ], @@ -119,13 +119,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.24.0.tgz", + "integrity": "sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==", "cpu": [ "arm64" ], @@ -136,13 +136,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.24.0.tgz", + "integrity": "sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==", "cpu": [ "x64" ], @@ -153,13 +153,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.24.0.tgz", + "integrity": "sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==", "cpu": [ "arm64" ], @@ -170,13 +170,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.24.0.tgz", + "integrity": "sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==", "cpu": [ "x64" ], @@ -187,13 +187,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.24.0.tgz", + "integrity": "sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==", "cpu": [ "arm64" ], @@ -204,13 +204,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.24.0.tgz", + "integrity": "sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==", "cpu": [ "x64" ], @@ -221,13 +221,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.24.0.tgz", + "integrity": "sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==", "cpu": [ "arm" ], @@ -238,13 +238,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.24.0.tgz", + "integrity": "sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==", "cpu": [ "arm64" ], @@ -255,13 +255,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.24.0.tgz", + "integrity": "sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==", "cpu": [ "ia32" ], @@ -272,13 +272,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.24.0.tgz", + "integrity": "sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==", "cpu": [ "loong64" ], @@ -289,13 +289,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.24.0.tgz", + "integrity": "sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==", "cpu": [ "mips64el" ], @@ -306,13 +306,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.24.0.tgz", + "integrity": "sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==", "cpu": [ "ppc64" ], @@ -323,13 +323,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.24.0.tgz", + "integrity": "sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==", "cpu": [ "riscv64" ], @@ -340,13 +340,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.24.0.tgz", + "integrity": "sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==", "cpu": [ "s390x" ], @@ -357,13 +357,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.24.0.tgz", + "integrity": "sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==", "cpu": [ "x64" ], @@ -374,13 +374,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.24.0.tgz", + "integrity": "sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==", "cpu": [ "x64" ], @@ -391,13 +391,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.24.0.tgz", + "integrity": "sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.24.0.tgz", + "integrity": "sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==", "cpu": [ "x64" ], @@ -408,13 +425,13 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.24.0.tgz", + "integrity": "sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==", "cpu": [ "x64" ], @@ -425,13 +442,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.24.0.tgz", + "integrity": "sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==", "cpu": [ "arm64" ], @@ -442,13 +459,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.24.0.tgz", + "integrity": "sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==", "cpu": [ "ia32" ], @@ -459,13 +476,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.24.0.tgz", + "integrity": "sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==", "cpu": [ "x64" ], @@ -476,7 +493,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@ioredis/commands": { @@ -484,31 +501,51 @@ "resolved": "https://registry.npmjs.org/@ioredis/commands/-/commands-1.2.0.tgz", "integrity": "sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==" }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "license": "ISC", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", - "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "license": "MIT", "dependencies": { - "@jridgewell/set-array": "^1.0.1", + "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", - "@jridgewell/trace-mapping": "^0.3.9" + "@jridgewell/trace-mapping": "^0.3.24" }, "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", - "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", "engines": { "node": ">=6.0.0" } }, "node_modules/@jridgewell/set-array": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", - "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "license": "MIT", "engines": { "node": ">=6.0.0" } @@ -520,19 +557,15 @@ "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.18", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", - "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "3.1.0", - "@jridgewell/sourcemap-codec": "1.4.14" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.14", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", - "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==" - }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -565,6 +598,16 @@ "node": ">= 8" } }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=14" + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.25.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.25.0.tgz", @@ -864,98 +907,98 @@ "license": "MIT" }, "node_modules/@vitejs/plugin-vue": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.0.tgz", - "integrity": "sha512-7n7KdUEtx/7Yl7I/WVAMZ1bEb0eVvXF3ummWTeLcs/9gvo9pJhuLdouSXGjdZ/MKD1acf1I272+X0RMua4/R3g==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.1.tgz", + "integrity": "sha512-cxh314tzaWwOLqVes2gnnCtvBDcM1UMdn+iFR+UjAn411dPT3tOmqrJjbMd7koZpMAmBM/GqeV4n9ge7JSiJJQ==", "dev": true, "license": "MIT", "engines": { "node": "^18.0.0 || >=20.0.0" }, "peerDependencies": { - "vite": "^5.0.0", + "vite": "^5.0.0 || ^6.0.0", "vue": "^3.2.25" } }, "node_modules/@vue/compiler-core": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.12.tgz", - "integrity": "sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.13.tgz", + "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==", "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.25.3", - "@vue/shared": "3.5.12", + "@vue/shared": "3.5.13", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-core/node_modules/@vue/shared": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", - "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", "dev": true, "license": "MIT" }, "node_modules/@vue/compiler-dom": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz", - "integrity": "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz", + "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-core": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/compiler-dom/node_modules/@vue/shared": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", - "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", "dev": true, "license": "MIT" }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz", - "integrity": "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.13.tgz", + "integrity": "sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==", "dev": true, "license": "MIT", "dependencies": { "@babel/parser": "^7.25.3", - "@vue/compiler-core": "3.5.12", - "@vue/compiler-dom": "3.5.12", - "@vue/compiler-ssr": "3.5.12", - "@vue/shared": "3.5.12", + "@vue/compiler-core": "3.5.13", + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13", "estree-walker": "^2.0.2", "magic-string": "^0.30.11", - "postcss": "^8.4.47", + "postcss": "^8.4.48", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-sfc/node_modules/@vue/shared": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", - "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", "dev": true, "license": "MIT" }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.12.tgz", - "integrity": "sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.13.tgz", + "integrity": "sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-dom": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/compiler-ssr/node_modules/@vue/shared": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", - "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", "dev": true, "license": "MIT" }, @@ -968,81 +1011,81 @@ } }, "node_modules/@vue/runtime-core": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.12.tgz", - "integrity": "sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.13.tgz", + "integrity": "sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==", "dev": true, "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/reactivity": "3.5.13", + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-core/node_modules/@vue/reactivity": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.12.tgz", - "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", "dev": true, "license": "MIT", "dependencies": { - "@vue/shared": "3.5.12" + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-core/node_modules/@vue/shared": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", - "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", "dev": true, "license": "MIT" }, "node_modules/@vue/runtime-dom": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.12.tgz", - "integrity": "sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.13.tgz", + "integrity": "sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==", "dev": true, "license": "MIT", "dependencies": { - "@vue/reactivity": "3.5.12", - "@vue/runtime-core": "3.5.12", - "@vue/shared": "3.5.12", + "@vue/reactivity": "3.5.13", + "@vue/runtime-core": "3.5.13", + "@vue/shared": "3.5.13", "csstype": "^3.1.3" } }, "node_modules/@vue/runtime-dom/node_modules/@vue/reactivity": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.12.tgz", - "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.13.tgz", + "integrity": "sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==", "dev": true, "license": "MIT", "dependencies": { - "@vue/shared": "3.5.12" + "@vue/shared": "3.5.13" } }, "node_modules/@vue/runtime-dom/node_modules/@vue/shared": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", - "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", "dev": true, "license": "MIT" }, "node_modules/@vue/server-renderer": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.12.tgz", - "integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.13.tgz", + "integrity": "sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-ssr": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { - "vue": "3.5.12" + "vue": "3.5.13" } }, "node_modules/@vue/server-renderer/node_modules/@vue/shared": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", - "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", "dev": true, "license": "MIT" }, @@ -1065,18 +1108,43 @@ "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==" }, "node_modules/alpinejs": { - "version": "3.14.3", - "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.3.tgz", - "integrity": "sha512-cL8JBEDAm4UeVjTN5QnFl8QgMGUwxFn1GvQvu3RtfAHUrAPRahGihrsWpKnEK9L0QMqsAPk/R8MylMWKHaK33A==", + "version": "3.14.7", + "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.7.tgz", + "integrity": "sha512-ScnbydNBcWVnCiVupD3wWUvoMPm8244xkvDNMxVCspgmap9m4QuJ7pjc+77UtByU+1+Ejg0wzYkP4mQaOMcvng==", "license": "MIT", "dependencies": { "@vue/reactivity": "~3.1.1" } }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "license": "MIT" }, "node_modules/anymatch": { "version": "3.1.3", @@ -1140,9 +1208,9 @@ } }, "node_modules/axios": { - "version": "1.7.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", - "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", "dev": true, "license": "MIT", "dependencies": { @@ -1154,7 +1222,8 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" }, "node_modules/binary-extensions": { "version": "2.2.0", @@ -1165,12 +1234,12 @@ } }, "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/braces": { @@ -1247,15 +1316,10 @@ "license": "CC-BY-4.0" }, "node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", "dependencies": { "anymatch": "~3.1.2", "braces": "~3.0.2", @@ -1268,6 +1332,9 @@ "engines": { "node": ">= 8.10.0" }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, "optionalDependencies": { "fsevents": "~2.3.2" } @@ -1291,6 +1358,24 @@ "node": ">=0.10.0" } }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1307,14 +1392,24 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", "engines": { "node": ">= 6" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } }, "node_modules/cssesc": { "version": "3.0.0", @@ -1377,6 +1472,12 @@ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, "node_modules/electron-to-chromium": { "version": "1.5.55", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.55.tgz", @@ -1384,6 +1485,12 @@ "dev": true, "license": "ISC" }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", @@ -1398,9 +1505,9 @@ } }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.0.tgz", + "integrity": "sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1408,32 +1515,33 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.24.0", + "@esbuild/android-arm": "0.24.0", + "@esbuild/android-arm64": "0.24.0", + "@esbuild/android-x64": "0.24.0", + "@esbuild/darwin-arm64": "0.24.0", + "@esbuild/darwin-x64": "0.24.0", + "@esbuild/freebsd-arm64": "0.24.0", + "@esbuild/freebsd-x64": "0.24.0", + "@esbuild/linux-arm": "0.24.0", + "@esbuild/linux-arm64": "0.24.0", + "@esbuild/linux-ia32": "0.24.0", + "@esbuild/linux-loong64": "0.24.0", + "@esbuild/linux-mips64el": "0.24.0", + "@esbuild/linux-ppc64": "0.24.0", + "@esbuild/linux-riscv64": "0.24.0", + "@esbuild/linux-s390x": "0.24.0", + "@esbuild/linux-x64": "0.24.0", + "@esbuild/netbsd-x64": "0.24.0", + "@esbuild/openbsd-arm64": "0.24.0", + "@esbuild/openbsd-x64": "0.24.0", + "@esbuild/sunos-x64": "0.24.0", + "@esbuild/win32-arm64": "0.24.0", + "@esbuild/win32-ia32": "0.24.0", + "@esbuild/win32-x64": "0.24.0" } }, "node_modules/escalade": { @@ -1518,6 +1626,22 @@ } } }, + "node_modules/foreground-child": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/form-data": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", @@ -1545,11 +1669,6 @@ "url": "https://github.com/sponsors/rawify" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" - }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -1564,24 +1683,29 @@ } }, "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", + "license": "ISC", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -1598,15 +1722,16 @@ "node": ">=10.13.0" } }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", "dependencies": { - "function-bind": "^1.1.1" + "function-bind": "^1.1.2" }, "engines": { - "node": ">= 0.4.0" + "node": ">= 0.4" } }, "node_modules/immutable": { @@ -1617,20 +1742,6 @@ "optional": true, "peer": true }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, "node_modules/ioredis": { "version": "5.4.1", "resolved": "https://registry.npmjs.org/ioredis/-/ioredis-5.4.1.tgz", @@ -1666,11 +1777,15 @@ } }, "node_modules/is-core-module": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", - "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", + "license": "MIT", "dependencies": { - "has": "^1.0.3" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -1684,6 +1799,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -1703,18 +1827,40 @@ "node": ">=0.12.0" } }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/jackspeak": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", + "license": "BlueOak-1.0.0", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, "node_modules/jiti": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", - "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "version": "1.21.6", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz", + "integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==", + "license": "MIT", "bin": { "jiti": "bin/jiti.js" } }, "node_modules/laravel-echo": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.17.0.tgz", - "integrity": "sha512-uf+BVZMkXc7+pzxS2dG5v1P+MT3yWS+/9oDSJUcQ4KqnDKLYfM1lc7yUmnxvLtwPksGuQJv6XBtzvWLHSEheNQ==", + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.17.1.tgz", + "integrity": "sha512-ORWc4vDfnBj/Oe5ThZ5kYyGItRjLDqAQUyhD/7UhehUOqc+s5x9HEBjtMVludNMP6VuXw6t7Uxt8bp63kaTofg==", "dev": true, "license": "MIT", "engines": { @@ -1722,9 +1868,9 @@ } }, "node_modules/laravel-vite-plugin": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.0.6.tgz", - "integrity": "sha512-B34OqmZc/rV1KvSjst8SsUm/LKHsuDusw8jiZCIhlnTHXbXnK89JUM9pTJuk6E/Vc/1DT2gX7qNfhipak1WS8w==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-1.1.1.tgz", + "integrity": "sha512-HMZXpoSs1OR+7Lw1+g4Iy/s3HF3Ldl8KxxYT2Ot8pEB4XB/QRuZeWgDYJdu552UN03YRSRNK84CLC9NzYRtncA==", "dev": true, "license": "MIT", "dependencies": { @@ -1735,24 +1881,29 @@ "clean-orphaned-assets": "bin/clean.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "peerDependencies": { - "vite": "^5.0.0" + "vite": "^5.0.0 || ^6.0.0" } }, "node_modules/lilconfig": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", - "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" } }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "license": "MIT" }, "node_modules/lodash.castarray": { "version": "4.4.0", @@ -1779,10 +1930,16 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, + "node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "license": "ISC" + }, "node_modules/magic-string": { - "version": "0.30.12", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.12.tgz", - "integrity": "sha512-Ea8I3sQMVXr8JhN4z+H/d8zwo+tYDgHE9+5G4Wnrwhs0gaK9fXTKx0Tw5Xwsd/bCPTTZNRAdpyzvoeORe9LYpw==", + "version": "0.30.15", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.15.tgz", + "integrity": "sha512-zXeaYRgZ6ldS1RJJUrMrYgNJ4fdwnyI6tVqoiIhyCyv5IVTK9BU8Ic2l253GGETQHxI4HNUwhJ3fjDhKqEoaAw==", "dev": true, "license": "MIT", "dependencies": { @@ -1839,14 +1996,27 @@ } }, "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.1" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", + "license": "ISC", + "engines": { + "node": ">=16 || 14 >=14.17" } }, "node_modules/ms": { @@ -1858,6 +2028,7 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "license": "MIT", "dependencies": { "any-promise": "^1.0.0", "object-assign": "^4.0.1", @@ -1865,15 +2036,16 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "version": "3.3.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -1909,6 +2081,7 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1921,20 +2094,19 @@ "node": ">= 6" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/path-is-absolute": { + "node_modules/package-json-from-dist": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", + "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", + "license": "BlueOak-1.0.0" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, "node_modules/path-parse": { @@ -1942,6 +2114,22 @@ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, + "node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1968,9 +2156,10 @@ } }, "node_modules/pirates": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.5.tgz", - "integrity": "sha512-8V9+HQPupnaXMA23c5hvl69zXvTwTzyAYasnkb0Tts4XvO4CliqONMOnvlq26rkhLC3nWDFBJf73LU1e1VZLaQ==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "license": "MIT", "engines": { "node": ">= 6" } @@ -2038,20 +2227,27 @@ } }, "node_modules/postcss-load-config": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", - "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "lilconfig": "^2.0.5", - "yaml": "^2.1.1" + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" }, "engines": { "node": ">= 14" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, "peerDependencies": { "postcss": ">=8.0.9", "ts-node": ">=9.0.0" @@ -2066,27 +2262,35 @@ } }, "node_modules/postcss-nested": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", - "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", "dependencies": { - "postcss-selector-parser": "^6.0.11" + "postcss-selector-parser": "^6.1.1" }, "engines": { "node": ">=12.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, "peerDependencies": { "postcss": "^8.2.14" } }, "node_modules/postcss-selector-parser": { - "version": "6.0.13", - "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", - "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "version": "6.1.2", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz", + "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==", + "license": "MIT", "dependencies": { "cssesc": "^3.0.0", "util-deprecate": "^1.0.2" @@ -2173,11 +2377,12 @@ } }, "node_modules/resolve": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", - "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "license": "MIT", "dependencies": { - "is-core-module": "^2.11.0", + "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, @@ -2276,6 +2481,39 @@ "node": ">=14.0.0" } }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -2290,14 +2528,111 @@ "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", "integrity": "sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==" }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/sucrase": { - "version": "3.32.0", - "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", - "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "license": "MIT", "dependencies": { "@jridgewell/gen-mapping": "^0.3.2", "commander": "^4.0.0", - "glob": "7.1.6", + "glob": "^10.3.10", "lines-and-columns": "^1.1.6", "mz": "^2.7.0", "pirates": "^4.0.1", @@ -2308,7 +2643,7 @@ "sucrase-node": "bin/sucrase-node" }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" } }, "node_modules/supports-preserve-symlinks-flag": { @@ -2336,33 +2671,33 @@ } }, "node_modules/tailwindcss": { - "version": "3.4.14", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz", - "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==", + "version": "3.4.16", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.16.tgz", + "integrity": "sha512-TI4Cyx7gDiZ6r44ewaJmt0o6BrMCT5aK5e0rmJ/G9Xq3w7CX/5VXl/zIPEJZFUK5VEqwByyhqNPycPlvcK4ZNw==", "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", - "chokidar": "^3.5.3", + "chokidar": "^3.6.0", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "fast-glob": "^3.3.0", + "fast-glob": "^3.3.2", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jiti": "^1.21.0", - "lilconfig": "^2.1.0", - "micromatch": "^4.0.5", + "jiti": "^1.21.6", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", - "picocolors": "^1.0.0", - "postcss": "^8.4.23", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", "postcss-import": "^15.1.0", "postcss-js": "^4.0.1", - "postcss-load-config": "^4.0.1", - "postcss-nested": "^6.0.1", - "postcss-selector-parser": "^6.0.11", - "resolve": "^1.22.2", - "sucrase": "^3.32.0" + "postcss-load-config": "^4.0.2", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" }, "bin": { "tailwind": "lib/cli.js", @@ -2376,6 +2711,7 @@ "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "license": "MIT", "dependencies": { "any-promise": "^1.0.0" } @@ -2384,6 +2720,7 @@ "version": "1.6.0", "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" }, @@ -2405,7 +2742,8 @@ "node_modules/ts-interface-checker": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", - "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "license": "Apache-2.0" }, "node_modules/tweetnacl": { "version": "1.0.3", @@ -2450,21 +2788,21 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "5.4.11", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", - "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.3.tgz", + "integrity": "sha512-Cmuo5P0ENTN6HxLSo6IHsjCLn/81Vgrp81oaiFFMRa8gGDj5xEjIcEpf2ZymZtZR8oU0P2JX5WuUp/rlXcHkAw==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.24.0", + "postcss": "^8.4.49", + "rollup": "^4.23.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -2473,19 +2811,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -2506,6 +2850,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, @@ -2520,17 +2870,17 @@ } }, "node_modules/vue": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.12.tgz", - "integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", + "integrity": "sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==", "dev": true, "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.5.12", - "@vue/compiler-sfc": "3.5.12", - "@vue/runtime-dom": "3.5.12", - "@vue/server-renderer": "3.5.12", - "@vue/shared": "3.5.12" + "@vue/compiler-dom": "3.5.13", + "@vue/compiler-sfc": "3.5.13", + "@vue/runtime-dom": "3.5.13", + "@vue/server-renderer": "3.5.13", + "@vue/shared": "3.5.13" }, "peerDependencies": { "typescript": "*" @@ -2542,21 +2892,126 @@ } }, "node_modules/vue/node_modules/@vue/shared": { - "version": "3.5.12", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", - "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "version": "3.5.13", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.13.tgz", + "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==", "dev": true, "license": "MIT" }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } }, "node_modules/yaml": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", - "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.6.1.tgz", + "integrity": "sha512-7r0XPzioN/Q9kXBro/XPnA6kznR73DHq+GXh5ON7ZozRO6aMjbmiBuKste2wslTFkC5d1dw0GooOCepZXJ2SAg==", + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, "engines": { "node": ">= 14" } diff --git a/package.json b/package.json index a174d73c72..50197706a1 100644 --- a/package.json +++ b/package.json @@ -7,24 +7,24 @@ "build": "vite build" }, "devDependencies": { - "@vitejs/plugin-vue": "5.2.0", + "@vitejs/plugin-vue": "5.2.1", "autoprefixer": "10.4.20", - "axios": "1.7.7", - "laravel-echo": "1.17.0", - "laravel-vite-plugin": "1.0.6", + "axios": "1.7.9", + "laravel-echo": "1.17.1", + "laravel-vite-plugin": "1.1.1", "postcss": "8.4.49", "pusher-js": "8.4.0-rc2", "tailwind-scrollbar": "^3.1.0", - "tailwindcss": "3.4.14", - "vite": "5.4.11", - "vue": "3.5.12" + "tailwindcss": "3.4.16", + "vite": "6.0.3", + "vue": "3.5.13" }, "dependencies": { "@tailwindcss/forms": "0.5.9", "@tailwindcss/typography": "0.5.15", "@xterm/addon-fit": "^0.10.0", "@xterm/xterm": "^5.5.0", - "alpinejs": "3.14.3", + "alpinejs": "3.14.7", "ioredis": "5.4.1" } } diff --git a/public/svgs/checkmate.png b/public/svgs/checkmate.png new file mode 100644 index 0000000000..f0f31466b2 Binary files /dev/null and b/public/svgs/checkmate.png differ diff --git a/public/svgs/documenso.png b/public/svgs/documenso.png new file mode 100644 index 0000000000..baa6365323 Binary files /dev/null and b/public/svgs/documenso.png differ diff --git a/public/svgs/dolibarr.png b/public/svgs/dolibarr.png new file mode 100644 index 0000000000..f09119afc9 Binary files /dev/null and b/public/svgs/dolibarr.png differ diff --git a/public/svgs/fileflows.svg b/public/svgs/fileflows.svg new file mode 100644 index 0000000000..ee37e9ba24 --- /dev/null +++ b/public/svgs/fileflows.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/public/svgs/heimdall.svg b/public/svgs/heimdall.svg new file mode 100644 index 0000000000..6ecfa8457b --- /dev/null +++ b/public/svgs/heimdall.svg @@ -0,0 +1,11 @@ + + + + background + + + + Layer 1 + + + \ No newline at end of file diff --git a/public/svgs/invoiceninja.png b/public/svgs/invoiceninja.png new file mode 100644 index 0000000000..5141cd4d1e Binary files /dev/null and b/public/svgs/invoiceninja.png differ diff --git a/public/svgs/kuzzle.png b/public/svgs/kuzzle.png new file mode 100644 index 0000000000..a7bf37029f Binary files /dev/null and b/public/svgs/kuzzle.png differ diff --git a/public/svgs/nexus.png b/public/svgs/nexus.png new file mode 100644 index 0000000000..823ea4e867 Binary files /dev/null and b/public/svgs/nexus.png differ diff --git a/public/svgs/pairdrop.png b/public/svgs/pairdrop.png new file mode 100644 index 0000000000..b0e9ee5d0d Binary files /dev/null and b/public/svgs/pairdrop.png differ diff --git a/public/svgs/penpot.svg b/public/svgs/penpot.svg new file mode 100644 index 0000000000..6439292bd2 --- /dev/null +++ b/public/svgs/penpot.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/public/svgs/whoogle.png b/public/svgs/whoogle.png new file mode 100644 index 0000000000..0d89d25f24 Binary files /dev/null and b/public/svgs/whoogle.png differ diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php index 00cf95a449..a61a8fb324 100644 --- a/resources/views/auth/forgot-password.blade.php +++ b/resources/views/auth/forgot-password.blade.php @@ -9,7 +9,7 @@
- @if (is_transactional_emails_active()) + @if (is_transactional_emails_enabled())
@csrf diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index d6c3edf84d..9404ed2c55 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -12,7 +12,7 @@ @endforeach
@endif -
+
@csrf @env('local') diff --git a/resources/views/components/forms/checkbox.blade.php b/resources/views/components/forms/checkbox.blade.php index fb244962d2..39704a122a 100644 --- a/resources/views/components/forms/checkbox.blade.php +++ b/resources/views/components/forms/checkbox.blade.php @@ -5,8 +5,8 @@ 'disabled' => false, 'instantSave' => false, 'value' => null, + 'domValue' => null, 'checked' => false, - 'hideLabel' => false, 'fullWidth' => false, ]) @@ -14,26 +14,32 @@ 'flex flex-row items-center gap-4 pr-2 py-1 form-control min-w-fit dark:hover:bg-coolgray-100', 'w-full' => $fullWidth, ])> - @if (!$hideLabel) - - @endif +
diff --git a/resources/views/components/notification/navbar.blade.php b/resources/views/components/notification/navbar.blade.php index 0fbbc69a23..447a385c5f 100644 --- a/resources/views/components/notification/navbar.blade.php +++ b/resources/views/components/notification/navbar.blade.php @@ -7,14 +7,22 @@ href="{{ route('notifications.email') }}"> - - - + + + + + + + + +
diff --git a/resources/views/emails/docker-cleanup-failed.blade.php b/resources/views/emails/docker-cleanup-failed.blade.php new file mode 100644 index 0000000000..a12f20fa8e --- /dev/null +++ b/resources/views/emails/docker-cleanup-failed.blade.php @@ -0,0 +1,8 @@ + +Docker Cleanup on {{ $name }} FAILED with the following error: + +
+{{ $text }}
+
+ +
diff --git a/resources/views/emails/docker-cleanup-success.blade.php b/resources/views/emails/docker-cleanup-success.blade.php new file mode 100644 index 0000000000..8671d74c81 --- /dev/null +++ b/resources/views/emails/docker-cleanup-success.blade.php @@ -0,0 +1,9 @@ + +Docker Cleanup on {{ $name }} succeeded with the following message: + + +
+{{ $text }}
+
+ +
diff --git a/resources/views/emails/scheduled-task-success.blade.php b/resources/views/emails/scheduled-task-success.blade.php new file mode 100644 index 0000000000..44ef8fa589 --- /dev/null +++ b/resources/views/emails/scheduled-task-success.blade.php @@ -0,0 +1,9 @@ + +Scheduled task ({{ $task->name }}) completed successfully with the following output: + +
+{{ $output }}
+
+ +Click [here]({{ $url }}) to view the task. +
diff --git a/resources/views/emails/waitlist-confirmation.blade.php b/resources/views/emails/waitlist-confirmation.blade.php deleted file mode 100644 index afd22916a7..0000000000 --- a/resources/views/emails/waitlist-confirmation.blade.php +++ /dev/null @@ -1,7 +0,0 @@ - -Someone added this email to the Coolify Cloud's waitlist. [Click here]({{ $confirmation_url }}) to confirm! - -The link will expire in {{ config('constants.waitlist.expiration') }} minutes. - -You have no idea what [Coolify Cloud](https://coolify.io) is or this waitlist? [Click here]({{ $cancel_url }}) to remove you from the waitlist. - diff --git a/resources/views/emails/waitlist-invitation.blade.php b/resources/views/emails/waitlist-invitation.blade.php deleted file mode 100644 index de8d646507..0000000000 --- a/resources/views/emails/waitlist-invitation.blade.php +++ /dev/null @@ -1,3 +0,0 @@ - -You have been invited to join the Coolify Cloud: [Get Started]({{ $loginLink }}) - diff --git a/resources/views/livewire/notifications/discord.blade.php b/resources/views/livewire/notifications/discord.blade.php index af6f98b0ab..6caf311769 100644 --- a/resources/views/livewire/notifications/discord.blade.php +++ b/resources/views/livewire/notifications/discord.blade.php @@ -3,7 +3,7 @@ Notifications | Coolify - +

Discord

@@ -12,7 +12,11 @@ @if ($discordEnabled) - Send Test Notifications + Send Test Notification + + @else + + Send Test Notification @endif
@@ -20,24 +24,58 @@ - @if ($discordEnabled) -

Subscribe to events

-
- @if (isDev()) - - @endif - - - - - +

Notification Settings

+

+ Select events for which you would like to receive Discord notifications. +

+
+
+

Deployments

+
+ + + +
+
+
+

Backups

+
+ + +
+
+
+

Scheduled Tasks

+
+ + +
+
+
+

Server

+
+ + + + + +
- @endif +
diff --git a/resources/views/livewire/notifications/email.blade.php b/resources/views/livewire/notifications/email.blade.php index 182c73d6a5..1977df5160 100644 --- a/resources/views/livewire/notifications/email.blade.php +++ b/resources/views/livewire/notifications/email.blade.php @@ -9,25 +9,27 @@ Save - @if (isInstanceAdmin() && !$useInstanceEmailSettings) - - Copy from Instance Settings - - @endif - @if (isEmailEnabled($team) && auth()->user()->isAdminFromSession() && isTestEmailEnabled($team)) - -
- - - Send Email - - -
+ @if (auth()->user()->isAdminFromSession()) + @if ($team->isNotificationEnabled('email')) + +
+ + + Send Email + + +
+ @else + + Send Test Email + + @endif @endif @if (!isCloud())
-
@endif @@ -37,17 +39,22 @@ + @if (isInstanceAdmin() && !$useInstanceEmailSettings) + + Copy from Instance Settings + + @endif @endif @if (isCloud())
-
@endif @if (!$useInstanceEmailSettings)
-
+

SMTP Server

@@ -55,15 +62,19 @@
- +
- + + + + +
@@ -74,7 +85,7 @@
-
+

Resend

@@ -82,7 +93,8 @@
- +
@@ -95,20 +107,55 @@
@endif - @if (isEmailEnabled($team) || $useInstanceEmailSettings) -

Subscribe to events

-
- @if (isDev()) - - @endif - - - - - +

Notification Settings

+

+ Select events for which you would like to receive email notifications. +

+
+
+

Deployments

+
+ + + +
- @endif +
+

Backups

+
+ + +
+
+
+

Scheduled Tasks

+
+ + +
+
+
+

Server

+
+ + + + + +
+
+
diff --git a/resources/views/livewire/notifications/pushover.blade.php b/resources/views/livewire/notifications/pushover.blade.php new file mode 100644 index 0000000000..c8dd777d8b --- /dev/null +++ b/resources/views/livewire/notifications/pushover.blade.php @@ -0,0 +1,86 @@ +
+ + Notifications | Coolify + + +
+
+

Pushover

+ + Save + + @if ($pushoverEnabled) + + Send Test Notification + + @else + + Send Test Notification + + @endif +
+
+ +
+
+ + +
+
+

Notification Settings

+

+ Select events for which you would like to receive Pushover notifications. +

+
+
+

Deployments

+
+ + + +
+
+
+

Backups

+
+ + +
+
+
+

Scheduled Tasks

+
+ + +
+
+
+

Server

+
+ + + + + +
+
+
+
diff --git a/resources/views/livewire/notifications/slack.blade.php b/resources/views/livewire/notifications/slack.blade.php new file mode 100644 index 0000000000..28bfe509fe --- /dev/null +++ b/resources/views/livewire/notifications/slack.blade.php @@ -0,0 +1,79 @@ +
+ + Notifications | Coolify + + +
+
+

Slack

+ + Save + + @if ($slackEnabled) + + Send Test Notification + + @else + + Send Test Notification + + @endif +
+
+ +
+ + +

Notification Settings

+

+ Select events for which you would like to receive Slack notifications. +

+
+
+

Deployments

+
+ + + +
+
+
+

Backups

+
+ + +
+
+
+

Scheduled Tasks

+
+ + +
+
+
+

Server

+
+ + + + + +
+
+
+
diff --git a/resources/views/livewire/notifications/telegram.blade.php b/resources/views/livewire/notifications/telegram.blade.php index 76378ada1a..ecc9360fa0 100644 --- a/resources/views/livewire/notifications/telegram.blade.php +++ b/resources/views/livewire/notifications/telegram.blade.php @@ -3,7 +3,7 @@ Notifications | Coolify -
+

Telegram

@@ -12,7 +12,11 @@ @if ($telegramEnabled) - Send Test Notifications + Send Test Notification + + @else + + Send Test Notification @endif
@@ -20,61 +24,143 @@
- - +
- @if ($telegramEnabled) -

Subscribe to events

-
- @if (isDev()) -
-

Test Notification

- - + +

Notification Settings

+

+ Select events for which you would like to receive Telegram notifications. +

+
+
+

Deployments

+
+
+
+
- @endif -
-

Container Status Changes

- - +
-
-

Application Deployments

- - +
+
+ +
+
-
-

Database Backup Status

- - +
+
+ +
+
-
-

Scheduled Tasks Status

- - +
+
+
+

Backups

+
+
+
+ +
+
-
-

Server Disk Usage

- + +
+
+ +
+
- @endif - +
+ +
+

Scheduled Tasks

+
+
+
+ +
+ +
+ +
+
+ +
+ +
+
+
+ +
+

Server

+
+
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+
+ +
+ +
+ + +
+
+ +
+ +
+
+
+
diff --git a/resources/views/livewire/project/application/advanced.blade.php b/resources/views/livewire/project/application/advanced.blade.php index 6658c0ed2d..f3fb0485ff 100644 --- a/resources/views/livewire/project/application/advanced.blade.php +++ b/resources/views/livewire/project/application/advanced.blade.php @@ -13,6 +13,8 @@ helper="Allow to automatically deploy Preview Deployments for all opened PR's.

Closing a PR will delete Preview Deployments." instantSave id="isPreviewDeploymentsEnabled" label="Preview Deployments" /> @endif + diff --git a/resources/views/livewire/project/application/deployment/show.blade.php b/resources/views/livewire/project/application/deployment/show.blade.php index 9d9301d5c4..92ed72981e 100644 --- a/resources/views/livewire/project/application/deployment/show.blade.php +++ b/resources/views/livewire/project/application/deployment/show.blade.php @@ -19,7 +19,7 @@ }, toggleScroll() { this.alwaysScroll = !this.alwaysScroll; - + if (this.alwaysScroll) { this.intervalId = setInterval(() => { const screen = document.getElementById('screen'); @@ -58,30 +58,34 @@ class="dark:text-warning">{{ Str::headline(data_get($application_deployment_queu
-
+