diff --git a/.dockerignore b/.dockerignore index 2eba3cb46..0adca0b32 100644 --- a/.dockerignore +++ b/.dockerignore @@ -24,3 +24,4 @@ yarn-error.log /.ssh .ignition.json .env.dusk.local +docker/coolify-realtime/node_modules diff --git a/.gitattributes b/.gitattributes index fcb21d396..c48a5898b 100644 --- a/.gitattributes +++ b/.gitattributes @@ -8,4 +8,4 @@ /.github export-ignore CHANGELOG.md export-ignore -.styleci.yml export-ignore +.styleci.yml export-ignore \ No newline at end of file diff --git a/.github/workflows/coolify-production-build.yml b/.github/workflows/coolify-production-build.yml index 7017b4897..5271143ec 100644 --- a/.github/workflows/coolify-production-build.yml +++ b/.github/workflows/coolify-production-build.yml @@ -11,7 +11,7 @@ on: - docker/coolify-helper/Dockerfile - docker/coolify-realtime/Dockerfile - docker/testing-host/Dockerfile - - templates/service-templates.json + - templates/** env: GITHUB_REGISTRY: ghcr.io @@ -41,7 +41,7 @@ jobs: - name: Get Version id: version run: | - echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT] + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT - name: Build and Push Image uses: docker/build-push-action@v6 @@ -76,7 +76,7 @@ jobs: - name: Get Version id: version run: | - echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT] + echo "VERSION=$(docker run --rm -v "$(pwd):/app" -w /app php:8.2-alpine3.16 php bootstrap/getVersion.php)"|xargs >> $GITHUB_OUTPUT - name: Build and Push Image uses: docker/build-push-action@v6 diff --git a/.github/workflows/coolify-realtime-next.yml b/.github/workflows/coolify-realtime-next.yml index 7e937d17a..ef247170f 100644 --- a/.github/workflows/coolify-realtime-next.yml +++ b/.github/workflows/coolify-realtime-next.yml @@ -8,6 +8,7 @@ on: - docker/coolify-realtime/Dockerfile - docker/coolify-realtime/terminal-server.js - docker/coolify-realtime/package.json + - docker/coolify-realtime/package-lock.json - docker/coolify-realtime/soketi-entrypoint.sh env: diff --git a/.github/workflows/coolify-realtime.yml b/.github/workflows/coolify-realtime.yml index 97bfd52eb..9654a21b0 100644 --- a/.github/workflows/coolify-realtime.yml +++ b/.github/workflows/coolify-realtime.yml @@ -8,6 +8,7 @@ on: - docker/coolify-realtime/Dockerfile - docker/coolify-realtime/terminal-server.js - docker/coolify-realtime/package.json + - docker/coolify-realtime/package-lock.json - docker/coolify-realtime/soketi-entrypoint.sh env: diff --git a/.github/workflows/coolify-staging-build.yml b/.github/workflows/coolify-staging-build.yml index 6e4d4adc3..2c57a36a3 100644 --- a/.github/workflows/coolify-staging-build.yml +++ b/.github/workflows/coolify-staging-build.yml @@ -11,7 +11,7 @@ on: - docker/coolify-helper/Dockerfile - docker/coolify-realtime/Dockerfile - docker/testing-host/Dockerfile - - templates/service-templates.json + - templates/** env: GITHUB_REGISTRY: ghcr.io diff --git a/.gitignore b/.gitignore index 1a021ab3e..d7ee7e96c 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ _ide_helper_models.php scripts/load-test/* .ignition.json .env.dusk.local +docker/coolify-realtime/node_modules +.DS_Store diff --git a/.gitpod.yml b/.gitpod.yml deleted file mode 100644 index 6fd6797b5..000000000 --- a/.gitpod.yml +++ /dev/null @@ -1,65 +0,0 @@ -tasks: - - name: Setup Spin environment and Composer dependencies - # Fix because of https://github.com/gitpod-io/gitpod/issues/16614 - before: sudo curl -o /usr/local/bin/docker-compose -fsSL https://github.com/docker/compose/releases/download/v2.16.0/docker-compose-linux-$(uname -m) - init: | - cp .env.development.example .env && - sed -i "s#APP_URL=http://localhost#APP_URL=$(gp url 8000)#g" .env - sed -i "s#USERID=#USERID=33333#g" .env - sed -i "s#GROUPID=#GROUPID=33333#g" .env - composer install --ignore-platform-reqs - ./vendor/bin/spin up -d - ./vendor/bin/spin exec -u webuser coolify php artisan key:generate - ./vendor/bin/spin exec -u webuser coolify php artisan storage:link - ./vendor/bin/spin exec -u webuser coolify php artisan migrate:fresh --seed - cat .coolify-logo - gp sync-done spin-is-ready - - - name: Install Node dependencies and run Vite - command: | - echo "Waiting for Sail environment to boot up." - gp sync-await spin-is-ready - ./vendor/bin/spin exec vite npm install - ./vendor/bin/spin exec vite npm run dev -- --host - - - name: Laravel Queue Worker, listening to code changes - command: | - echo "Waiting for Sail environment to boot up." - gp sync-await spin-is-ready - ./vendor/bin/spin exec -u webuser coolify php artisan queue:listen - -ports: - - port: 5432 - onOpen: ignore - name: PostgreSQL - visibility: public - - port: 5173 - onOpen: ignore - visibility: public - name: Node Server for Vite - - port: 8000 - onOpen: ignore - visibility: public - name: Coolify - -# Configure vscode -vscode: - extensions: - - bmewburn.vscode-intelephense-client - - ikappas.composer - - ms-azuretools.vscode-docker - - ecmel.vscode-html-css - - MehediDracula.php-namespace-resolver - - wmaurer.change-case - - Equinusocio.vsc-community-material-theme - - EditorConfig.EditorConfig - - streetsidesoftware.code-spell-checker - - rangav.vscode-thunder-client - - PKief.material-icon-theme - - cierra.livewire-vscode - - lennardv.livewire-goto-updated - - bradlc.vscode-tailwindcss - - heybourn.headwind - - adrianwilczynski.alpine-js-intellisense - - amiralizadeh9480.laravel-extra-intellisense - - shufo.vscode-blade-formatter diff --git a/README.md b/README.md index 14a741088..dac48d127 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ # About the Project -Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc. +Coolify is an open-source & self-hostable alternative to Heroku / Netlify / Vercel / etc. It helps you manage your servers, applications, and databases on your own hardware; you only need an SSH connection. You can manage VPS, Bare Metal, Raspberry PIs, and anything else. @@ -22,6 +22,9 @@ curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash ``` You can find the installation script source [here](./scripts/install.sh). +> [!NOTE] +> Please refer to the [docs](https://coolify.io/docs/installation) for more information about the installation. + # Support Contact us at [coolify.io/docs/contact](https://coolify.io/docs/contact). @@ -37,21 +40,21 @@ Special thanks to our biggest sponsors! ### Special Sponsors -![image](https://github.com/user-attachments/assets/c95a07df-7c5a-4e77-a35a-81f25fcbece1) +![image](https://github.com/user-attachments/assets/726fb63e-c3b8-4260-b3ac-06780605ec5d) * [CCCareers](https://cccareers.org/) - A career development platform connecting coding bootcamp graduates with job opportunities in the tech industry. * [Hetzner](http://htznr.li/CoolifyXHetzner) - A German web hosting company offering affordable dedicated servers, cloud services, and web hosting solutions. * [Logto](https://logto.io/?ref=coolify) - An open-source authentication and authorization solution for building secure login systems and managing user identities. +* [Tolgee](https://tolgee.io/?ref=coolify) - Developer & translator friendly web-based localization platform. * [BC Direct](https://bc.direct/?ref=coolify.io) - A digital marketing agency specializing in e-commerce solutions and online business growth strategies. * [QuantCDN](https://www.quantcdn.io/?ref=coolify.io) - A content delivery network (CDN) optimizing website performance through global content distribution. * [Arcjet](https://arcjet.com/?ref=coolify.io) - A cloud-based platform providing real-time protection against API abuse and bot attacks. * [SupaGuide](https://supa.guide/?ref=coolify.io) - A comprehensive resource hub offering guides and tutorials for web development using Supabase. +* [GoldenVM](https://billing.goldenvm.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes. * [Tigris](https://tigrisdata.com/?ref=coolify.io) - A fully managed serverless object storage service compatible with Amazon S3 API. Offers high performance, scalability, and built-in search capabilities for efficient data management. -* [Fractal Networks](https://fractalnetworks.co/?ref=coolify.io) - A decentralized network infrastructure company focusing on secure and private communication solutions. * [Advin](https://coolify.ad.vin/?ref=coolify.io) - A digital advertising agency specializing in programmatic advertising and data-driven marketing strategies. * [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets. * [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers. -* [Latitude](https://latitude.sh/?ref=coolify.io) - A cloud computing platform offering bare metal servers and cloud instances for developers and businesses. * [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities. * [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries. * [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools. @@ -60,6 +63,7 @@ Special thanks to our biggest sponsors! * [Juxtdigital](https://juxtdigital.dev/?ref=coolify.io) - A digital agency offering web development, design, and digital marketing services for businesses. * [Saasykit](https://saasykit.com/?ref=coolify.io) - A Laravel-based boilerplate providing essential components and features for building SaaS applications quickly. * [Massivegrid](https://massivegrid.com/?ref=coolify.io) - A cloud hosting provider offering scalable infrastructure solutions for businesses of all sizes. +* [LiquidWeb](https://liquidweb.com/?utm_source=coolify.io) - Fast web hosting provider. ## Github Sponsors ($40+) @@ -87,7 +91,11 @@ Special thanks to our biggest sponsors! Paweł Pierścionek Michael Mazurczak Formbricks -Adith Suhas +StartupFame +Jonas Jaeger +JP +Evercam +Web3 Career ## Organizations @@ -121,7 +129,6 @@ By subscribing to the cloud version, you get the Coolify server for the same pri - Better support - Less maintenance for you - # Recognitions

@@ -138,6 +145,13 @@ By subscribing to the cloud version, you get the Coolify server for the same pri coollabsio%2Fcoolify | Trendshift +# Core Maintainers + +| Andras Bacsai | 🏔️ Peak | +|------------|------------| +| Andras Bacsai | peaklabs-dev | +| | | + # Repo Activity ![Alt](https://repobeats.axiom.co/api/embed/eab1c8066f9c59d0ad37b76c23ebb5ccac4278ae.svg "Repobeats analytics image") diff --git a/RELEASE.md b/RELEASE.md index d9f05f17d..bc159b040 100644 --- a/RELEASE.md +++ b/RELEASE.md @@ -1,6 +1,6 @@ # Coolify Release Guide -This guide outlines the release process for Coolify, intended for developers and those interested in understanding how releases are managed and deployed. +This guide outlines the release process for Coolify, intended for developers and those interested in understanding how Coolify releases are managed and deployed. ## Table of Contents - [Release Process](#release-process) @@ -19,19 +19,19 @@ This guide outlines the release process for Coolify, intended for developers and - Improvements, fixes, and new features are developed on the `next` branch or separate feature branches. 2. **Merging to `main`** - - Once ready, changes are merged from the `next` branch into the `main` branch. + - Once ready, changes are merged from the `next` branch into the `main` branch (via a pull request). 3. **Building the Release** - - After merging to `main`, GitHub Actions automatically builds release images for all architectures and pushes them to the GitHub Container Registry with the version tag and the `latest` tag. + - After merging to `main`, GitHub Actions automatically builds release images for all architectures and pushes them to the GitHub Container Registry and Docker Hub with the specific version tag and the `latest` tag. 4. **Creating a GitHub Release** - A new GitHub release is manually created with details of the changes made in the version. 5. **Updating the CDN** - - To make a new version publicly available, the version information on the CDN needs to be updated: [https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json) + - To make a new version publicly available, the version information on the CDN needs to be updated manually. After that the new version number will be available at [https://cdn.coollabs.io/coolify/versions.json](https://cdn.coollabs.io/coolify/versions.json). > [!NOTE] -> The CDN update may not occur immediately after the GitHub release. It can take hours or even days due to additional testing, stability checks, or potential hotfixes. **The update becomes available only after the CDN is updated.** +> The CDN update may not occur immediately after the GitHub release. It can take hours or even days due to additional testing, stability checks, or potential hotfixes. **The update becomes available only after the CDN is updated. After the CDN is updated, a discord announcement will be made in the Production Release channel.** ## Version Types @@ -39,10 +39,10 @@ This guide outlines the release process for Coolify, intended for developers and

Stable (coming soon) - **Stable** - - The production version suitable for stable, production environments (generally recommended). - - **Update Frequency:** Every 2 to 4 weeks, with more frequent possible hotfixes. + - The production version suitable for stable, production environments (recommended). + - **Update Frequency:** Every 2 to 4 weeks, with more frequent possible fixes. - **Release Size:** Larger but less frequent releases. Multiple nightly versions are consolidated into a single stable release. - - **Versioning Scheme:** Follows semantic versioning (e.g., `v4.0.0`). + - **Versioning Scheme:** Follows semantic versioning (e.g., `v4.0.0`, `4.1.0`, etc.). - **Installation Command:** ```bash curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash @@ -57,7 +57,7 @@ This guide outlines the release process for Coolify, intended for developers and - The latest development version, suitable for testing the latest changes and experimenting with new features. - **Update Frequency:** Daily or bi-weekly updates. - **Release Size:** Smaller, more frequent releases. - - **Versioning Scheme:** TO BE DETERMINED + - **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-nightly.1`, `4.1.0-nightly.2`, etc.). - **Installation Command:** ```bash curl -fsSL https://cdn.coollabs.io/coolify-nightly/install.sh | bash -s next @@ -73,11 +73,11 @@ This guide outlines the release process for Coolify, intended for developers and - **Purpose:** Allows users to test and provide feedback on new features and changes before they become stable. - **Update Frequency:** Available if we think beta testing is necessary. - **Release Size:** Same size as stable release as it will become the next stabe release after some time. - - **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-beta.1`). + - **Versioning Scheme:** Follows semantic versioning (e.g., `4.1.0-beta.1`, `4.1.0-beta.2`, etc.). - **Installation Command:** - ```bash + ```bash curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash - ``` + ``` @@ -117,12 +117,15 @@ When a new version is released and a new GitHub release is created, it doesn't i > [!IMPORTANT] > The cloud version of Coolify may be several versions behind the latest GitHub releases even if the CDN is updated. This is intentional to ensure stability and reliability for cloud users and Andras will manully update the cloud version when the update is ready. -## Manually Update to Specific Versions +## Manually Update/ Downgrade to Specific Versions > [!CAUTION] -> Updating to unreleased versions is not recommended and may cause issues. Use at your own risk! +> Updating to unreleased versions is not recommended and can cause issues. -To update your Coolify instance to a specific (unreleased) version, use the following command: +> [!IMPORTANT] +> Downgrading is supported but not recommended and can cause issues because of database migrations and other changes. + +To update your Coolify instance to a specific version, use the following command: ```bash curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash -s diff --git a/SECURITY.md b/SECURITY.md index ad3a4addd..0711bf5b5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,15 +2,24 @@ ## Supported Versions -Use this section to tell people about which versions of your project are -currently being supported with security updates. +Currently supported, maintained and updated versions: -| Version | Supported | -| ------- | ------------------ | -| > 4 | :white_check_mark: | -| 3 | :x: | +| Version | Supported | Support Status | +| ------- | ------------------ | -------------- | +| 4.x | :white_check_mark: | Active Development & Security Updates | +| < 4.0 | :x: | End of Life (no security updates) | +## Security Updates + +We take security seriously. Security updates are released as soon as possible after a vulnerability is discovered and verified. ## Reporting a Vulnerability -If you have any vulnerability please report at hi@coollabs.io +If you discover a security vulnerability, please follow these steps: + +1. **DO NOT** disclose the vulnerability publicly. +2. Send a detailed report to: `hi@coollabs.io`. +3. Include in your report: + - A description of the vulnerability + - Steps to reproduce the issue + - Potential impact diff --git a/app/Actions/Application/IsHorizonQueueEmpty.php b/app/Actions/Application/IsHorizonQueueEmpty.php new file mode 100644 index 000000000..17966b8a0 --- /dev/null +++ b/app/Actions/Application/IsHorizonQueueEmpty.php @@ -0,0 +1,37 @@ +getRecent(); + if ($recent) { + $running = $recent->filter(function ($job) use ($hostname) { + $payload = json_decode($job->payload); + $tags = data_get($payload, 'tags'); + + return $job->status != 'completed' && + $job->status != 'failed' && + isset($tags) && + is_array($tags) && + in_array('server:'.$hostname, $tags); + }); + if ($running->count() > 0) { + echo 'false'; + + return false; + } + } + echo 'true'; + + return true; + } +} diff --git a/app/Actions/Application/StopApplication.php b/app/Actions/Application/StopApplication.php index cab7e45f0..642b4ba45 100644 --- a/app/Actions/Application/StopApplication.php +++ b/app/Actions/Application/StopApplication.php @@ -10,6 +10,8 @@ class StopApplication { use AsAction; + public string $jobQueue = 'high'; + public function handle(Application $application, bool $previewDeployments = false, bool $dockerCleanup = true) { try { diff --git a/app/Actions/CoolifyTask/PrepareCoolifyTask.php b/app/Actions/CoolifyTask/PrepareCoolifyTask.php index 6676b7937..3f76a2e3c 100644 --- a/app/Actions/CoolifyTask/PrepareCoolifyTask.php +++ b/app/Actions/CoolifyTask/PrepareCoolifyTask.php @@ -3,7 +3,6 @@ namespace App\Actions\CoolifyTask; use App\Data\CoolifyTaskArgs; -use App\Enums\ActivityTypes; use App\Jobs\CoolifyTask; use Spatie\Activitylog\Models\Activity; @@ -47,11 +46,7 @@ class PrepareCoolifyTask call_event_on_finish: $this->remoteProcessArgs->call_event_on_finish, call_event_data: $this->remoteProcessArgs->call_event_data, ); - if ($this->remoteProcessArgs->type === ActivityTypes::COMMAND->value) { - dispatch($job)->onQueue('high'); - } else { - dispatch($job); - } + dispatch($job); $this->activity->refresh(); return $this->activity; diff --git a/app/Actions/CoolifyTask/RunRemoteProcess.php b/app/Actions/CoolifyTask/RunRemoteProcess.php index 51c5a1f9e..981b81378 100644 --- a/app/Actions/CoolifyTask/RunRemoteProcess.php +++ b/app/Actions/CoolifyTask/RunRemoteProcess.php @@ -9,6 +9,7 @@ use App\Jobs\ApplicationDeploymentJob; use App\Models\Server; use Illuminate\Process\ProcessResult; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Process; use Spatie\Activitylog\Models\Activity; @@ -124,6 +125,7 @@ class RunRemoteProcess ])); } } catch (\Throwable $e) { + Log::error('Error calling event: '.$e->getMessage()); } } diff --git a/app/Actions/Database/StartDatabase.php b/app/Actions/Database/StartDatabase.php index 869a88521..e2fa6fc87 100644 --- a/app/Actions/Database/StartDatabase.php +++ b/app/Actions/Database/StartDatabase.php @@ -16,6 +16,8 @@ class StartDatabase { use AsAction; + public string $jobQueue = 'high'; + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database) { $server = $database->destination->server; @@ -49,7 +51,7 @@ class StartDatabase break; } if ($database->is_public && $database->public_port) { - StartDatabaseProxy::dispatch($database)->onQueue('high'); + StartDatabaseProxy::dispatch($database); } return $activity; diff --git a/app/Actions/Database/StartDatabaseProxy.php b/app/Actions/Database/StartDatabaseProxy.php index d7a3bc697..3ddf6c036 100644 --- a/app/Actions/Database/StartDatabaseProxy.php +++ b/app/Actions/Database/StartDatabaseProxy.php @@ -18,6 +18,8 @@ class StartDatabaseProxy { use AsAction; + public string $jobQueue = 'high'; + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|ServiceDatabase $database) { $internalPort = null; diff --git a/app/Actions/Database/StartMongodb.php b/app/Actions/Database/StartMongodb.php index 6d67bdde6..26a0f82d0 100644 --- a/app/Actions/Database/StartMongodb.php +++ b/app/Actions/Database/StartMongodb.php @@ -25,6 +25,10 @@ class StartMongodb $container_name = $this->database->uuid; $this->configuration_dir = database_configuration_dir().'/'.$container_name; + if (isDev()) { + $this->configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$container_name; + } + $this->commands = [ "echo 'Starting {$database->name}.'", "mkdir -p $this->configuration_dir", diff --git a/app/Actions/Database/StopDatabaseProxy.php b/app/Actions/Database/StopDatabaseProxy.php index 0a166d24a..9ee794351 100644 --- a/app/Actions/Database/StopDatabaseProxy.php +++ b/app/Actions/Database/StopDatabaseProxy.php @@ -18,6 +18,8 @@ class StopDatabaseProxy { use AsAction; + public string $jobQueue = 'high'; + public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|ServiceDatabase|StandaloneDragonfly|StandaloneClickhouse $database) { $server = data_get($database, 'destination.server'); diff --git a/app/Actions/Docker/GetContainersStatus.php b/app/Actions/Docker/GetContainersStatus.php index 95c22efc1..8aae910a9 100644 --- a/app/Actions/Docker/GetContainersStatus.php +++ b/app/Actions/Docker/GetContainersStatus.php @@ -7,7 +7,6 @@ use App\Actions\Shared\ComplexStatusCheck; use App\Models\ApplicationPreview; use App\Models\Server; use App\Models\ServiceDatabase; -use App\Notifications\Container\ContainerRestarted; use Illuminate\Support\Arr; use Illuminate\Support\Collection; use Lorisleiva\Actions\Concerns\AsAction; @@ -16,6 +15,8 @@ class GetContainersStatus { use AsAction; + public string $jobQueue = 'high'; + public $applications; public ?Collection $containers; @@ -107,6 +108,8 @@ class GetContainersStatus $statusFromDb = $preview->status; if ($statusFromDb !== $containerStatus) { $preview->update(['status' => $containerStatus]); + } else { + $preview->update(['last_online_at' => now()]); } } else { //Notify user that this container should not be there. @@ -118,6 +121,8 @@ class GetContainersStatus $statusFromDb = $application->status; if ($statusFromDb !== $containerStatus) { $application->update(['status' => $containerStatus]); + } else { + $application->update(['last_online_at' => now()]); } } else { //Notify user that this container should not be there. @@ -160,7 +165,10 @@ class GetContainersStatus $statusFromDb = $database->status; if ($statusFromDb !== $containerStatus) { $database->update(['status' => $containerStatus]); + } else { + $database->update(['last_online_at' => now()]); } + if ($isPublic) { $foundTcpProxy = $this->containers->filter(function ($value, $key) use ($uuid) { if ($this->server->isSwarm()) { @@ -171,7 +179,7 @@ class GetContainersStatus })->first(); if (! $foundTcpProxy) { StartDatabaseProxy::run($database); - $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); + // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); } } } else { @@ -202,6 +210,8 @@ class GetContainersStatus if ($statusFromDb !== $containerStatus) { // ray('Updating status: ' . $containerStatus); $service->update(['status' => $containerStatus]); + } else { + $service->update(['last_online_at' => now()]); } } } diff --git a/app/Actions/License/CheckResaleLicense.php b/app/Actions/License/CheckResaleLicense.php deleted file mode 100644 index 26a1ff7bf..000000000 --- a/app/Actions/License/CheckResaleLicense.php +++ /dev/null @@ -1,66 +0,0 @@ -update([ - 'is_resale_license_active' => true, - ]); - - return; - } - // if (!$settings->resale_license) { - // return; - // } - $base_url = config('coolify.license_url'); - $instance_id = config('app.id'); - $data = Http::withHeaders([ - 'Accept' => 'application/json', - ])->get("$base_url/lemon/validate", [ - 'license_key' => $settings->resale_license, - 'instance_id' => $instance_id, - ])->json(); - if (data_get($data, 'valid') === true && data_get($data, 'license_key.status') === 'active') { - $settings->update([ - 'is_resale_license_active' => true, - ]); - - return; - } - $data = Http::withHeaders([ - 'Accept' => 'application/json', - ])->get("$base_url/lemon/activate", [ - 'license_key' => $settings->resale_license, - 'instance_id' => $instance_id, - ])->json(); - if (data_get($data, 'activated') === true) { - $settings->update([ - 'is_resale_license_active' => true, - ]); - - return; - } - if (data_get($data, 'license_key.status') === 'active') { - throw new \Exception('Invalid license key.'); - } - throw new \Exception('Cannot activate license key.'); - } catch (\Throwable $e) { - $settings->update([ - 'resale_license' => null, - 'is_resale_license_active' => false, - ]); - throw $e; - } - } -} diff --git a/app/Actions/Proxy/CheckProxy.php b/app/Actions/Proxy/CheckProxy.php index d64804758..6c8dd5234 100644 --- a/app/Actions/Proxy/CheckProxy.php +++ b/app/Actions/Proxy/CheckProxy.php @@ -4,6 +4,7 @@ namespace App\Actions\Proxy; use App\Enums\ProxyTypes; use App\Models\Server; +use Illuminate\Support\Facades\Log; use Lorisleiva\Actions\Concerns\AsAction; use Symfony\Component\Yaml\Yaml; @@ -29,7 +30,7 @@ class CheckProxy if (is_null($proxyType) || $proxyType === 'NONE' || $server->proxy->force_stop) { return false; } - ['uptime' => $uptime, 'error' => $error] = $server->validateConnection(false); + ['uptime' => $uptime, 'error' => $error] = $server->validateConnection(); if (! $uptime) { throw new \Exception($error); } @@ -88,6 +89,7 @@ class CheckProxy $portsToCheck = []; } } catch (\Exception $e) { + Log::error('Error checking proxy: '.$e->getMessage()); } if (count($portsToCheck) === 0) { return false; diff --git a/app/Actions/Server/CleanupDocker.php b/app/Actions/Server/CleanupDocker.php index dc6ac12bf..0349ead89 100644 --- a/app/Actions/Server/CleanupDocker.php +++ b/app/Actions/Server/CleanupDocker.php @@ -9,11 +9,13 @@ class CleanupDocker { use AsAction; + public string $jobQueue = 'high'; + public function handle(Server $server) { $settings = instanceSettings(); $helperImageVersion = data_get($settings, 'helper_version'); - $helperImage = config('coolify.helper_image'); + $helperImage = config('constants.coolify.helper_image'); $helperImageWithVersion = "$helperImage:$helperImageVersion"; $commands = [ diff --git a/app/Actions/Server/InstallDocker.php b/app/Actions/Server/InstallDocker.php index ba6c23ffc..cbcb20368 100644 --- a/app/Actions/Server/InstallDocker.php +++ b/app/Actions/Server/InstallDocker.php @@ -12,7 +12,7 @@ class InstallDocker public function handle(Server $server) { - $dockerVersion = config('constants.docker_install_version'); + $dockerVersion = config('constants.docker.minimum_required_version'); $supported_os_type = $server->validateOS(); if (! $supported_os_type) { throw new \Exception('Server OS type is not supported for automated installation. Please install Docker manually before continuing: documentation.'); diff --git a/app/Actions/Server/ServerCheck.php b/app/Actions/Server/ServerCheck.php index d9de1464e..5f9a1e357 100644 --- a/app/Actions/Server/ServerCheck.php +++ b/app/Actions/Server/ServerCheck.php @@ -14,7 +14,7 @@ use App\Models\Service; use App\Models\ServiceApplication; use App\Models\ServiceDatabase; use App\Notifications\Container\ContainerRestarted; -use Arr; +use Illuminate\Support\Arr; use Lorisleiva\Actions\Concerns\AsAction; class ServerCheck @@ -130,10 +130,10 @@ class ServerCheck if ($foundLogDrainContainer) { $status = data_get($foundLogDrainContainer, 'State.Status'); if ($status !== 'running') { - StartLogDrain::dispatch($this->server)->onQueue('high'); + StartLogDrain::dispatch($this->server); } } else { - StartLogDrain::dispatch($this->server)->onQueue('high'); + StartLogDrain::dispatch($this->server); } } @@ -259,7 +259,7 @@ class ServerCheck })->first(); if (! $foundTcpProxy) { StartDatabaseProxy::run($database); - $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); + // $this->server->team?->notify(new ContainerRestarted("TCP Proxy for {$database->name}", $this->server)); } } } diff --git a/app/Actions/Server/StartLogDrain.php b/app/Actions/Server/StartLogDrain.php index 0e8036cd9..0d28a0099 100644 --- a/app/Actions/Server/StartLogDrain.php +++ b/app/Actions/Server/StartLogDrain.php @@ -9,6 +9,8 @@ class StartLogDrain { use AsAction; + public string $jobQueue = 'high'; + public function handle(Server $server) { if ($server->settings->is_logdrain_newrelic_enabled) { @@ -169,7 +171,7 @@ Files: '); $license_key = $server->settings->logdrain_newrelic_license_key; $base_uri = $server->settings->logdrain_newrelic_base_uri; - $base_path = config('coolify.base_config_path'); + $base_path = config('constants.coolify.base_config_path'); $config_path = $base_path.'/log-drains'; $fluent_bit_config = $config_path.'/fluent-bit.conf'; diff --git a/app/Actions/Server/UpdateCoolify.php b/app/Actions/Server/UpdateCoolify.php index 3185c22b7..be9b4062c 100644 --- a/app/Actions/Server/UpdateCoolify.php +++ b/app/Actions/Server/UpdateCoolify.php @@ -4,6 +4,7 @@ namespace App\Actions\Server; use App\Jobs\PullHelperImageJob; use App\Models\Server; +use Illuminate\Support\Sleep; use Lorisleiva\Actions\Concerns\AsAction; class UpdateCoolify @@ -18,14 +19,19 @@ class UpdateCoolify public function handle($manual_update = false) { + if (isDev()) { + Sleep::for(10)->seconds(); + + return; + } $settings = instanceSettings(); $this->server = Server::find(0); if (! $this->server) { return; } - CleanupDocker::dispatch($this->server)->onQueue('high'); + CleanupDocker::dispatch($this->server); $this->latestVersion = get_latest_version_of_coolify(); - $this->currentVersion = config('version'); + $this->currentVersion = config('constants.coolify.version'); if (! $manual_update) { if (! $settings->is_auto_update_enabled) { return; @@ -44,19 +50,7 @@ class UpdateCoolify private function update() { - if (isDev()) { - remote_process([ - 'sleep 10', - ], $this->server); - - return; - } - - $all_servers = Server::all(); - $servers = $all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4'); - foreach ($servers as $server) { - PullHelperImageJob::dispatch($server); - } + PullHelperImageJob::dispatch($this->server); instant_remote_process(["docker pull -q ghcr.io/coollabsio/coolify:{$this->latestVersion}"], $this->server, false); diff --git a/app/Actions/Server/ValidateServer.php b/app/Actions/Server/ValidateServer.php index d0a4cd6be..55b37a77c 100644 --- a/app/Actions/Server/ValidateServer.php +++ b/app/Actions/Server/ValidateServer.php @@ -9,6 +9,8 @@ class ValidateServer { use AsAction; + public string $jobQueue = 'high'; + public ?string $uptime = null; public ?string $error = null; diff --git a/app/Actions/Service/DeleteService.php b/app/Actions/Service/DeleteService.php index 9c4b0349c..9b87454da 100644 --- a/app/Actions/Service/DeleteService.php +++ b/app/Actions/Service/DeleteService.php @@ -4,6 +4,7 @@ namespace App\Actions\Service; use App\Actions\Server\CleanupDocker; use App\Models\Service; +use Illuminate\Support\Facades\Log; use Lorisleiva\Actions\Concerns\AsAction; class DeleteService @@ -39,7 +40,8 @@ class DeleteService if (! empty($commands)) { foreach ($commands as $command) { $result = instant_remote_process([$command], $server, false); - if ($result !== 0) { + if ($result !== null && $result !== 0) { + Log::error('Error deleting volumes: '.$result); } } } diff --git a/app/Actions/Service/RestartService.php b/app/Actions/Service/RestartService.php index 1b6a5c32c..4151ea947 100644 --- a/app/Actions/Service/RestartService.php +++ b/app/Actions/Service/RestartService.php @@ -9,6 +9,8 @@ class RestartService { use AsAction; + public string $jobQueue = 'high'; + public function handle(Service $service) { StopService::run($service); diff --git a/app/Actions/Service/StartService.php b/app/Actions/Service/StartService.php index 82de066d7..1dfaf6c49 100644 --- a/app/Actions/Service/StartService.php +++ b/app/Actions/Service/StartService.php @@ -10,6 +10,8 @@ class StartService { use AsAction; + public string $jobQueue = 'high'; + public function handle(Service $service) { $service->saveComposeConfigs(); diff --git a/app/Actions/Service/StopService.php b/app/Actions/Service/StopService.php index 046d94ced..95b08b437 100644 --- a/app/Actions/Service/StopService.php +++ b/app/Actions/Service/StopService.php @@ -10,6 +10,8 @@ class StopService { use AsAction; + public string $jobQueue = 'high'; + public function handle(Service $service, bool $isDeleteOperation = false, bool $dockerCleanup = true) { try { diff --git a/app/Console/Commands/CleanupRedis.php b/app/Console/Commands/CleanupRedis.php index 5fc2b4e61..e16a82be4 100644 --- a/app/Console/Commands/CleanupRedis.php +++ b/app/Console/Commands/CleanupRedis.php @@ -13,7 +13,6 @@ class CleanupRedis extends Command public function handle() { - echo "Cleanup Redis keys.\n"; $prefix = config('database.redis.options.prefix'); $keys = Redis::connection()->keys('*:laravel*'); diff --git a/app/Console/Commands/CleanupStuckedResources.php b/app/Console/Commands/CleanupStuckedResources.php index 9d36ce9b8..def3d5a2c 100644 --- a/app/Console/Commands/CleanupStuckedResources.php +++ b/app/Console/Commands/CleanupStuckedResources.php @@ -30,7 +30,6 @@ class CleanupStuckedResources extends Command public function handle() { - echo "Running cleanup stucked resources.\n"; $this->cleanup_stucked_resources(); } diff --git a/app/Console/Commands/CloudCheckSubscription.php b/app/Console/Commands/CloudCheckSubscription.php new file mode 100644 index 000000000..6e237e84b --- /dev/null +++ b/app/Console/Commands/CloudCheckSubscription.php @@ -0,0 +1,49 @@ +get(); + foreach ($activeSubscribers as $team) { + $stripeSubscriptionId = $team->subscription->stripe_subscription_id; + $stripeInvoicePaid = $team->subscription->stripe_invoice_paid; + $stripeCustomerId = $team->subscription->stripe_customer_id; + if (! $stripeSubscriptionId) { + echo "Team {$team->id} has no subscription, but invoice status is: {$stripeInvoicePaid}\n"; + echo "Link on Stripe: https://dashboard.stripe.com/customers/{$stripeCustomerId}\n"; + + continue; + } + $subscription = $stripe->subscriptions->retrieve($stripeSubscriptionId); + if ($subscription->status === 'active') { + continue; + } + echo "Subscription {$stripeSubscriptionId} is not active ({$subscription->status})\n"; + echo "Link on Stripe: https://dashboard.stripe.com/subscriptions/{$stripeSubscriptionId}\n"; + } + } +} diff --git a/app/Console/Commands/Dev.php b/app/Console/Commands/Dev.php index f5f1233fe..962000d07 100644 --- a/app/Console/Commands/Dev.php +++ b/app/Console/Commands/Dev.php @@ -6,6 +6,7 @@ use App\Models\InstanceSettings; use Illuminate\Console\Command; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\Process; +use Symfony\Component\Yaml\Yaml; class Dev extends Command { @@ -31,19 +32,32 @@ class Dev extends Command { // Generate OpenAPI documentation echo "Generating OpenAPI documentation.\n"; - $process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']); + // https://github.com/OAI/OpenAPI-Specification/releases + $process = Process::run([ + '/var/www/html/vendor/bin/openapi', + 'app', + '-o', + 'openapi.yaml', + '--version', + '3.1.0', + ]); $error = $process->errorOutput(); $error = preg_replace('/^.*an object literal,.*$/m', '', $error); $error = preg_replace('/^\h*\v+/m', '', $error); echo $error; echo $process->output(); + // Convert YAML to JSON + $yaml = file_get_contents('openapi.yaml'); + $json = json_encode(Yaml::parse($yaml), JSON_PRETTY_PRINT); + file_put_contents('openapi.json', $json); + echo "Converted OpenAPI YAML to JSON.\n"; } public function init() { // Generate APP_KEY if not exists - if (empty(env('APP_KEY'))) { + if (empty(config('app.key'))) { echo "Generating APP_KEY.\n"; Artisan::call('key:generate'); } diff --git a/app/Console/Commands/Emails.php b/app/Console/Commands/Emails.php index cda4ca84f..f0e0e7fa0 100644 --- a/app/Console/Commands/Emails.php +++ b/app/Console/Commands/Emails.php @@ -187,7 +187,7 @@ class Emails extends Command 'team_id' => 0, ]); } - $this->mail = (new BackupSuccess($backup, $db))->toMail(); + // $this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail(); $this->sendEmail(); break; // case 'invitation-link': diff --git a/app/Console/Commands/Horizon.php b/app/Console/Commands/Horizon.php index 65a142d6e..655729ec9 100644 --- a/app/Console/Commands/Horizon.php +++ b/app/Console/Commands/Horizon.php @@ -12,8 +12,8 @@ class Horizon extends Command public function handle() { - if (config('coolify.is_horizon_enabled')) { - $this->info('Horizon is enabled. Starting.'); + if (config('constants.horizon.is_horizon_enabled')) { + $this->info('[x]: Horizon is enabled. Starting.'); $this->call('horizon'); exit(0); } else { diff --git a/app/Console/Commands/Init.php b/app/Console/Commands/Init.php index c802fb116..216262819 100644 --- a/app/Console/Commands/Init.php +++ b/app/Console/Commands/Init.php @@ -2,9 +2,9 @@ namespace App\Console\Commands; -use App\Actions\Server\StopSentinel; use App\Enums\ActivityTypes; use App\Enums\ApplicationDeploymentStatus; +use App\Jobs\CheckHelperImageJob; use App\Models\ApplicationDeploymentQueue; use App\Models\Environment; use App\Models\ScheduledDatabaseBackup; @@ -12,6 +12,7 @@ use App\Models\Server; use App\Models\StandalonePostgresql; use App\Models\User; use Illuminate\Console\Command; +use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Facades\File; use Illuminate\Support\Facades\Http; @@ -25,6 +26,8 @@ class Init extends Command public function handle() { + $this->optimize(); + if (isCloud() && ! $this->option('force-cloud')) { echo "Skipping init as we are on cloud and --force-cloud option is not set\n"; @@ -39,7 +42,6 @@ class Init extends Command } // Backward compatibility - // $this->disable_metrics(); $this->replace_slash_in_environment_name(); $this->restore_coolify_db_backup(); $this->update_user_emails(); @@ -53,16 +55,32 @@ class Init extends Command } 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 { + $this->pullHelperImage(); + } catch (\Throwable $e) { + // + } + if (isCloud()) { - $response = Http::retry(3, 1000)->get(config('constants.services.official')); - if ($response->successful()) { - $services = $response->json(); - File::put(base_path('templates/service-templates.json'), json_encode($services)); + try { + $this->pullTemplatesFromCDN(); + } catch (\Throwable $e) { + echo "Could not pull templates from CDN: {$e->getMessage()}\n"; + } + } + + if (! isCloud()) { + try { + $this->pullTemplatesFromCDN(); + } catch (\Throwable $e) { + echo "Could not pull templates from CDN: {$e->getMessage()}\n"; } - } else { try { $localhost = $this->servers->where('id', 0)->first(); $localhost->setupDynamicProxyConfiguration(); @@ -70,8 +88,8 @@ class Init extends Command echo "Could not setup dynamic configuration: {$e->getMessage()}\n"; } $settings = instanceSettings(); - if (! is_null(env('AUTOUPDATE', null))) { - if (env('AUTOUPDATE') == true) { + if (! is_null(config('constants.coolify.autoupdate', null))) { + if (config('constants.coolify.autoupdate') == true) { $settings->update(['is_auto_update_enabled' => true]); } else { $settings->update(['is_auto_update_enabled' => false]); @@ -80,19 +98,26 @@ class Init extends Command } } - // private function disable_metrics() - // { - // if (version_compare('4.0.0-beta.312', config('version'), '<=')) { - // foreach ($this->servers as $server) { - // if ($server->settings->is_metrics_enabled === true) { - // $server->settings->update(['is_metrics_enabled' => false]); - // } - // if ($server->isFunctional()) { - // StopSentinel::dispatch($server)->onQueue('high'); - // } - // } - // } - // } + private function pullHelperImage() + { + CheckHelperImageJob::dispatch(); + } + + private function pullTemplatesFromCDN() + { + $response = Http::retry(3, 1000)->get(config('constants.services.official')); + if ($response->successful()) { + $services = $response->json(); + File::put(base_path('templates/service-templates.json'), json_encode($services)); + } + } + + private function optimize() + { + echo "[1]: Optimizing Laravel (caching config, routes, views).\n"; + Artisan::call('optimize:clear'); + Artisan::call('optimize'); + } private function update_user_emails() { @@ -175,7 +200,7 @@ class Init extends Command private function restore_coolify_db_backup() { - if (version_compare('4.0.0-beta.179', config('version'), '<=')) { + if (version_compare('4.0.0-beta.179', config('constants.coolify.version'), '<=')) { try { $database = StandalonePostgresql::withTrashed()->find(0); if ($database && $database->trashed()) { @@ -203,19 +228,19 @@ class Init extends Command private function send_alive_signal() { $id = config('app.id'); - $version = config('version'); + $version = config('constants.coolify.version'); $settings = instanceSettings(); $do_not_track = data_get($settings, 'do_not_track'); if ($do_not_track == true) { - echo "Skipping alive as do_not_track is enabled\n"; + echo "[2]: Skipping sending live signal as do_not_track is enabled\n"; return; } try { Http::get("https://undead.coolify.io/v4/alive?appId=$id&version=$version"); - echo "I am alive!\n"; + echo "[2]: Sending live signal!\n"; } catch (\Throwable $e) { - echo "Error in alive: {$e->getMessage()}\n"; + echo "[2]: Error in sending live signal: {$e->getMessage()}\n"; } } @@ -239,7 +264,7 @@ class Init extends Command private function replace_slash_in_environment_name() { - if (version_compare('4.0.0-beta.298', config('version'), '<=')) { + if (version_compare('4.0.0-beta.298', config('constants.coolify.version'), '<=')) { $environments = Environment::all(); foreach ($environments as $environment) { if (str_contains($environment->name, '/')) { diff --git a/app/Console/Commands/OpenApi.php b/app/Console/Commands/OpenApi.php index e248aa2c0..6cbcb310c 100644 --- a/app/Console/Commands/OpenApi.php +++ b/app/Console/Commands/OpenApi.php @@ -15,7 +15,15 @@ class OpenApi extends Command { // Generate OpenAPI documentation echo "Generating OpenAPI documentation.\n"; - $process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']); + // https://github.com/OAI/OpenAPI-Specification/releases + $process = Process::run([ + '/var/www/html/vendor/bin/openapi', + 'app', + '-o', + 'openapi.yaml', + '--version', + '3.1.0', + ]); $error = $process->errorOutput(); $error = preg_replace('/^.*an object literal,.*$/m', '', $error); $error = preg_replace('/^\h*\v+/m', '', $error); diff --git a/app/Console/Commands/Scheduler.php b/app/Console/Commands/Scheduler.php index 304cb357d..9ee7b06e6 100644 --- a/app/Console/Commands/Scheduler.php +++ b/app/Console/Commands/Scheduler.php @@ -12,8 +12,8 @@ class Scheduler extends Command public function handle() { - if (config('coolify.is_scheduler_enabled')) { - $this->info('Scheduler is enabled. Starting.'); + if (config('constants.horizon.is_scheduler_enabled')) { + $this->info('[x]: Scheduler is enabled. Starting.'); $this->call('schedule:work'); exit(0); } else { diff --git a/app/Console/Commands/ServicesDelete.php b/app/Console/Commands/ServicesDelete.php index 1e5d5808c..b5a74166a 100644 --- a/app/Console/Commands/ServicesDelete.php +++ b/app/Console/Commands/ServicesDelete.php @@ -96,7 +96,7 @@ class ServicesDelete extends Command if (! $confirmed) { break; } - DeleteResourceJob::dispatch($toDelete)->onQueue('high'); + DeleteResourceJob::dispatch($toDelete); } } } @@ -122,7 +122,7 @@ class ServicesDelete extends Command if (! $confirmed) { return; } - DeleteResourceJob::dispatch($toDelete)->onQueue('high'); + DeleteResourceJob::dispatch($toDelete); } } } @@ -148,7 +148,7 @@ class ServicesDelete extends Command if (! $confirmed) { return; } - DeleteResourceJob::dispatch($toDelete)->onQueue('high'); + DeleteResourceJob::dispatch($toDelete); } } } diff --git a/app/Console/Commands/ServicesGenerate.php b/app/Console/Commands/ServicesGenerate.php index 1559e5f6d..b45707c5c 100644 --- a/app/Console/Commands/ServicesGenerate.php +++ b/app/Console/Commands/ServicesGenerate.php @@ -20,7 +20,10 @@ class ServicesGenerate extends Command public function handle(): int { - $serviceTemplatesJson = collect(glob(base_path('templates/compose/*.yaml'))) + $serviceTemplatesJson = collect(array_merge( + glob(base_path('templates/compose/*.yaml')), + glob(base_path('templates/compose/*.yml')) + )) ->mapWithKeys(function ($file): array { $file = basename($file); $parsed = $this->processFile($file); @@ -68,7 +71,7 @@ class ServicesGenerate extends Command 'slogan' => $data->get('slogan', str($file)->headline()), 'compose' => $compose, 'tags' => $tags, - 'logo' => $data->get('logo', 'svgs/coolify.png'), + 'logo' => $data->get('logo', 'svgs/default.webp'), 'minversion' => $data->get('minversion', '0.0.0'), ]; diff --git a/app/Console/Commands/SyncBunny.php b/app/Console/Commands/SyncBunny.php index 228467f88..df1903828 100644 --- a/app/Console/Commands/SyncBunny.php +++ b/app/Console/Commands/SyncBunny.php @@ -57,7 +57,7 @@ class SyncBunny extends Command PendingRequest::macro('storage', function ($fileName) use ($that) { $headers = [ - 'AccessKey' => env('BUNNY_STORAGE_API_KEY'), + 'AccessKey' => config('constants.bunny.storage_api_key'), 'Accept' => 'application/json', 'Content-Type' => 'application/octet-stream', ]; @@ -69,7 +69,7 @@ class SyncBunny extends Command }); PendingRequest::macro('purge', function ($url) use ($that) { $headers = [ - 'AccessKey' => env('BUNNY_API_KEY'), + 'AccessKey' => config('constants.bunny.api_key'), 'Accept' => 'application/json', ]; $that->info('Purging: '.$url); diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 3e3ae0834..19d22ae21 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -28,85 +28,100 @@ class Kernel extends ConsoleKernel { private $allServers; + private Schedule $scheduleInstance; + private InstanceSettings $settings; + private string $updateCheckFrequency; + + private string $instanceTimezone; + protected function schedule(Schedule $schedule): void { + $this->scheduleInstance = $schedule; $this->allServers = Server::where('ip', '!=', '1.2.3.4'); $this->settings = instanceSettings(); + $this->updateCheckFrequency = $this->settings->update_check_frequency ?: '0 * * * *'; - $schedule->job(new CleanupStaleMultiplexedConnections)->hourly(); + $this->instanceTimezone = $this->settings->instance_timezone ?: config('app.timezone'); + + if (validate_timezone($this->instanceTimezone) === false) { + $this->instanceTimezone = config('app.timezone'); + } + + // $this->scheduleInstance->job(new CleanupStaleMultiplexedConnections)->hourly(); if (isDev()) { // Instance Jobs - $schedule->command('horizon:snapshot')->everyMinute(); - $schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer(); - $schedule->job(new CheckHelperImageJob)->everyFiveMinutes()->onOneServer(); + $this->scheduleInstance->command('horizon:snapshot')->everyMinute(); + $this->scheduleInstance->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer(); + $this->scheduleInstance->job(new CheckHelperImageJob)->everyTenMinutes()->onOneServer(); // Server Jobs - $this->checkResources($schedule); + $this->checkResources(); - $this->checkScheduledBackups($schedule); - $this->checkScheduledTasks($schedule); + $this->checkScheduledBackups(); + $this->checkScheduledTasks(); - $schedule->command('uploads:clear')->everyTwoMinutes(); + $this->scheduleInstance->command('uploads:clear')->everyTwoMinutes(); } else { // Instance Jobs - $schedule->command('horizon:snapshot')->everyFiveMinutes(); - $schedule->command('cleanup:unreachable-servers')->daily()->onOneServer(); - $schedule->job(new PullTemplatesFromCDN)->cron($this->settings->update_check_frequency)->timezone($this->settings->instance_timezone)->onOneServer(); - $schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer(); - $this->scheduleUpdates($schedule); + $this->scheduleInstance->command('horizon:snapshot')->everyFiveMinutes(); + $this->scheduleInstance->command('cleanup:unreachable-servers')->daily()->onOneServer(); + + $this->scheduleInstance->job(new PullTemplatesFromCDN)->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer(); + + $this->scheduleInstance->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer(); + $this->scheduleUpdates(); // Server Jobs - $this->checkResources($schedule); + $this->checkResources(); - $this->pullImages($schedule); + $this->pullImages(); - $this->checkScheduledBackups($schedule); - $this->checkScheduledTasks($schedule); + $this->checkScheduledBackups(); + $this->checkScheduledTasks(); - $schedule->command('cleanup:database --yes')->daily(); - $schedule->command('uploads:clear')->everyTwoMinutes(); + $this->scheduleInstance->command('cleanup:database --yes')->daily(); + $this->scheduleInstance->command('uploads:clear')->everyTwoMinutes(); } } - private function pullImages($schedule): void + private function pullImages(): void { $servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get(); foreach ($servers as $server) { if ($server->isSentinelEnabled()) { - $schedule->job(function () use ($server) { + $this->scheduleInstance->job(function () use ($server) { CheckAndStartSentinelJob::dispatch($server); - })->cron($this->settings->update_check_frequency)->timezone($this->settings->instance_timezone)->onOneServer(); + })->cron($this->updateCheckFrequency)->timezone($this->instanceTimezone)->onOneServer(); } } - $schedule->job(new CheckHelperImageJob) - ->cron($this->settings->update_check_frequency) - ->timezone($this->settings->instance_timezone) + $this->scheduleInstance->job(new CheckHelperImageJob) + ->cron($this->updateCheckFrequency) + ->timezone($this->instanceTimezone) ->onOneServer(); } - private function scheduleUpdates($schedule): void + private function scheduleUpdates(): void { - $updateCheckFrequency = $this->settings->update_check_frequency; - $schedule->job(new CheckForUpdatesJob) - ->cron($updateCheckFrequency) - ->timezone($this->settings->instance_timezone) + $this->scheduleInstance->job(new CheckForUpdatesJob) + ->cron($this->updateCheckFrequency) + ->timezone($this->instanceTimezone) ->onOneServer(); if ($this->settings->is_auto_update_enabled) { $autoUpdateFrequency = $this->settings->auto_update_frequency; - $schedule->job(new UpdateCoolifyJob) + $this->scheduleInstance->job(new UpdateCoolifyJob) ->cron($autoUpdateFrequency) - ->timezone($this->settings->instance_timezone) + ->timezone($this->instanceTimezone) ->onOneServer(); } } - private function checkResources($schedule): void + private function checkResources(): void { if (isCloud()) { $servers = $this->allServers->whereHas('team.subscription')->get(); @@ -115,40 +130,46 @@ class Kernel extends ConsoleKernel } else { $servers = $this->allServers->get(); } - // $schedule->job(new \App\Jobs\ResourcesCheck)->everyMinute()->onOneServer(); foreach ($servers as $server) { - $serverTimezone = $server->settings->server_timezone; + $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); // Sentinel check $lastSentinelUpdate = $server->sentinel_updated_at; if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) { // Check container status every minute if Sentinel does not activated - $schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer(); - // $schedule->job(new \App\Jobs\ServerCheckNewJob($server))->everyMinute()->onOneServer(); + if (validate_timezone($serverTimezone) === false) { + $serverTimezone = config('app.timezone'); + } + if (isCloud()) { + $this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyFiveMinutes()->onOneServer(); + } else { + $this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyMinute()->onOneServer(); + } + // $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyFiveMinutes()->onOneServer(); // Check storage usage every 10 minutes if Sentinel does not activated - $schedule->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer(); + $this->scheduleInstance->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer(); } if ($server->settings->force_docker_cleanup) { - $schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer(); + $this->scheduleInstance->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer(); } else { - $schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer(); + $this->scheduleInstance->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer(); } // Cleanup multiplexed connections every hour - $schedule->job(new ServerCleanupMux($server))->hourly()->onOneServer(); + // $this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer(); // Temporary solution until we have better memory management for Sentinel if ($server->isSentinelEnabled()) { - $schedule->job(function () use ($server) { + $this->scheduleInstance->job(function () use ($server) { $server->restartContainer('coolify-sentinel'); })->daily()->onOneServer(); } } } - private function checkScheduledBackups($schedule): void + private function checkScheduledBackups(): void { $scheduled_backups = ScheduledDatabaseBackup::where('enabled', true)->get(); if ($scheduled_backups->isEmpty()) { @@ -166,18 +187,17 @@ class Kernel extends ConsoleKernel if (is_null($server)) { continue; } - $serverTimezone = $server->settings->server_timezone; if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) { $scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency]; } - $schedule->job(new DatabaseBackupJob( + $this->scheduleInstance->job(new DatabaseBackupJob( backup: $scheduled_backup - ))->cron($scheduled_backup->frequency)->timezone($serverTimezone)->onOneServer(); + ))->cron($scheduled_backup->frequency)->timezone($this->instanceTimezone)->onOneServer(); } } - private function checkScheduledTasks($schedule): void + private function checkScheduledTasks(): void { $scheduled_tasks = ScheduledTask::where('enabled', true)->get(); if ($scheduled_tasks->isEmpty()) { @@ -207,14 +227,13 @@ class Kernel extends ConsoleKernel if (! $server) { continue; } - $serverTimezone = $server->settings->server_timezone ?: config('app.timezone'); if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) { $scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency]; } - $schedule->job(new ScheduledTaskJob( + $this->scheduleInstance->job(new ScheduledTaskJob( task: $scheduled_task - ))->cron($scheduled_task->frequency)->timezone($serverTimezone)->onOneServer(); + ))->cron($scheduled_task->frequency)->timezone($this->instanceTimezone)->onOneServer(); } } diff --git a/app/Events/DatabaseProxyStopped.php b/app/Events/DatabaseProxyStopped.php index b457dc6a0..96b35a5ca 100644 --- a/app/Events/DatabaseProxyStopped.php +++ b/app/Events/DatabaseProxyStopped.php @@ -18,7 +18,7 @@ class DatabaseProxyStopped implements ShouldBroadcast public function __construct($teamId = null) { if (is_null($teamId)) { - $teamId = Auth::user()->currentTeam()->id ?? null; + $teamId = Auth::user()?->currentTeam()?->id ?? null; } if (is_null($teamId)) { throw new \Exception('Team id is null'); diff --git a/app/Events/ScheduledTaskDone.php b/app/Events/ScheduledTaskDone.php new file mode 100644 index 000000000..c8b5547f6 --- /dev/null +++ b/app/Events/ScheduledTaskDone.php @@ -0,0 +1,34 @@ +user()->currentTeam()->id ?? null; + } + if (is_null($teamId)) { + throw new \Exception('Team id is null'); + } + $this->teamId = $teamId; + } + + public function broadcastOn(): array + { + return [ + new PrivateChannel("team.{$this->teamId}"), + ]; + } +} diff --git a/app/Helpers/SshMultiplexingHelper.php b/app/Helpers/SshMultiplexingHelper.php index 1a2146799..8da476b9e 100644 --- a/app/Helpers/SshMultiplexingHelper.php +++ b/app/Helpers/SshMultiplexingHelper.php @@ -21,17 +21,14 @@ class SshMultiplexingHelper ]; } - public static function ensureMultiplexedConnection(Server $server) + public static function ensureMultiplexedConnection(Server $server): bool { if (! self::isMultiplexingEnabled()) { - return; + return false; } $sshConfig = self::serverSshConfiguration($server); $muxSocket = $sshConfig['muxFilename']; - $sshKeyLocation = $sshConfig['sshKeyLocation']; - - self::validateSshKey($sshKeyLocation); $checkCommand = "ssh -O check -o ControlPath=$muxSocket "; if (data_get($server, 'settings.is_cloudflare_tunnel')) { @@ -41,16 +38,17 @@ class SshMultiplexingHelper $process = Process::run($checkCommand); if ($process->exitCode() !== 0) { - self::establishNewMultiplexedConnection($server); + return self::establishNewMultiplexedConnection($server); } + + return true; } - public static function establishNewMultiplexedConnection(Server $server) + public static function establishNewMultiplexedConnection(Server $server): bool { $sshConfig = self::serverSshConfiguration($server); $sshKeyLocation = $sshConfig['sshKeyLocation']; $muxSocket = $sshConfig['muxFilename']; - $connectionTimeout = config('constants.ssh.connection_timeout'); $serverInterval = config('constants.ssh.server_interval'); $muxPersistTime = config('constants.ssh.mux_persist_time'); @@ -60,15 +58,14 @@ class SshMultiplexingHelper if (data_get($server, 'settings.is_cloudflare_tunnel')) { $establishCommand .= ' -o ProxyCommand="cloudflared access ssh --hostname %h" '; } - $establishCommand .= self::getCommonSshOptions($server, $sshKeyLocation, $connectionTimeout, $serverInterval); $establishCommand .= "{$server->user}@{$server->ip}"; - $establishProcess = Process::run($establishCommand); - if ($establishProcess->exitCode() !== 0) { - throw new \RuntimeException('Failed to establish multiplexed connection: '.$establishProcess->errorOutput()); + return false; } + + return true; } public static function removeMuxFile(Server $server) @@ -97,9 +94,8 @@ class SshMultiplexingHelper if ($server->isIpv6()) { $scp_command .= '-6 '; } - if (self::isMultiplexingEnabled()) { + if (self::isMultiplexingEnabled() && self::ensureMultiplexedConnection($server)) { $scp_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} "; - self::ensureMultiplexedConnection($server); } if (data_get($server, 'settings.is_cloudflare_tunnel')) { @@ -120,6 +116,9 @@ class SshMultiplexingHelper $sshConfig = self::serverSshConfiguration($server); $sshKeyLocation = $sshConfig['sshKeyLocation']; + + self::validateSshKey($server->privateKey); + $muxSocket = $sshConfig['muxFilename']; $timeout = config('constants.ssh.command_timeout'); @@ -127,9 +126,8 @@ class SshMultiplexingHelper $ssh_command = "timeout $timeout ssh "; - if (self::isMultiplexingEnabled()) { + if (self::isMultiplexingEnabled() && self::ensureMultiplexedConnection($server)) { $ssh_command .= "-o ControlMaster=auto -o ControlPath=$muxSocket -o ControlPersist={$muxPersistTime} "; - self::ensureMultiplexedConnection($server); } if (data_get($server, 'settings.is_cloudflare_tunnel')) { @@ -151,16 +149,17 @@ class SshMultiplexingHelper private static function isMultiplexingEnabled(): bool { - return config('constants.ssh.mux_enabled') && ! config('coolify.is_windows_docker_desktop'); + return config('constants.ssh.mux_enabled') && ! config('constants.coolify.is_windows_docker_desktop'); } - private static function validateSshKey(string $sshKeyLocation): void + private static function validateSshKey(PrivateKey $privateKey): void { - $checkKeyCommand = "ls $sshKeyLocation 2>/dev/null"; + $keyLocation = $privateKey->getKeyLocation(); + $checkKeyCommand = "ls $keyLocation 2>/dev/null"; $keyCheckProcess = Process::run($checkKeyCommand); if ($keyCheckProcess->exitCode() !== 0) { - throw new \RuntimeException("SSH key file not accessible: $sshKeyLocation"); + $privateKey->storeInFileSystem(); } } diff --git a/app/Http/Controllers/Api/ApplicationsController.php b/app/Http/Controllers/Api/ApplicationsController.php index f0eeb56d8..c69640970 100644 --- a/app/Http/Controllers/Api/ApplicationsController.php +++ b/app/Http/Controllers/Api/ApplicationsController.php @@ -636,7 +636,7 @@ class ApplicationsController extends Controller private function create_application(Request $request, $type) { - $allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image']; + $allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { return invalidTokenResponse(); @@ -676,6 +676,27 @@ class ApplicationsController extends Controller $githubAppUuid = $request->github_app_uuid; $useBuildServer = $request->use_build_server; $isStatic = $request->is_static; + $customNginxConfiguration = $request->custom_nginx_configuration; + + if (! is_null($customNginxConfiguration)) { + if (! isBase64Encoded($customNginxConfiguration)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.', + ], + ], 422); + } + $customNginxConfiguration = base64_decode($customNginxConfiguration); + if (mb_detect_encoding($customNginxConfiguration, 'ASCII', true) === false) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.', + ], + ], 422); + } + } $project = Project::whereTeamId($teamId)->whereUuid($request->project_uuid)->first(); if (! $project) { @@ -1203,7 +1224,7 @@ class ApplicationsController extends Controller $service->name = "service-$service->uuid"; $service->parse(isNew: true); if ($instantDeploy) { - StartService::dispatch($service)->onQueue('high'); + StartService::dispatch($service); } return response()->json(serializeApiResponse([ @@ -1358,7 +1379,7 @@ class ApplicationsController extends Controller deleteVolumes: $request->query->get('delete_volumes', true), dockerCleanup: $request->query->get('docker_cleanup', true), deleteConnectedNetworks: $request->query->get('delete_connected_networks', true) - )->onQueue('high'); + ); return response()->json([ 'message' => 'Application deletion request queued.', @@ -1500,7 +1521,7 @@ class ApplicationsController extends Controller ], 404); } $server = $application->destination->server; - $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server']; + $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy', 'use_build_server', 'custom_nginx_configuration']; $validationRules = [ 'name' => 'string|max:255', @@ -1512,6 +1533,7 @@ class ApplicationsController extends Controller 'docker_compose_domains' => 'array|nullable', 'docker_compose_custom_start_command' => 'string|nullable', 'docker_compose_custom_build_command' => 'string|nullable', + 'custom_nginx_configuration' => 'string|nullable', ]; $validationRules = array_merge($validationRules, sharedDataApplications()); $validator = customApiValidator($request->all(), $validationRules); @@ -1530,6 +1552,25 @@ class ApplicationsController extends Controller } } } + if ($request->has('custom_nginx_configuration')) { + if (! isBase64Encoded($request->custom_nginx_configuration)) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.', + ], + ], 422); + } + $customNginxConfiguration = base64_decode($request->custom_nginx_configuration); + if (mb_detect_encoding($customNginxConfiguration, 'ASCII', true) === false) { + return response()->json([ + 'message' => 'Validation failed.', + 'errors' => [ + 'custom_nginx_configuration' => 'The custom_nginx_configuration should be base64 encoded.', + ], + ], 422); + } + } $return = $this->validateDataApplications($request, $server); if ($return instanceof \Illuminate\Http\JsonResponse) { return $return; @@ -2482,7 +2523,7 @@ class ApplicationsController extends Controller if (! $application) { return response()->json(['message' => 'Application not found.'], 404); } - StopApplication::dispatch($application)->onQueue('high'); + StopApplication::dispatch($application); return response()->json( [ diff --git a/app/Http/Controllers/Api/DatabasesController.php b/app/Http/Controllers/Api/DatabasesController.php index eaa542a83..ce658d2a2 100644 --- a/app/Http/Controllers/Api/DatabasesController.php +++ b/app/Http/Controllers/Api/DatabasesController.php @@ -497,9 +497,9 @@ class DatabasesController extends Controller $database->update($request->all()); if ($whatToDoWithDatabaseProxy === 'start') { - StartDatabaseProxy::dispatch($database)->onQueue('high'); + StartDatabaseProxy::dispatch($database); } elseif ($whatToDoWithDatabaseProxy === 'stop') { - StopDatabaseProxy::dispatch($database)->onQueue('high'); + StopDatabaseProxy::dispatch($database); } return response()->json([ @@ -1151,7 +1151,7 @@ class DatabasesController extends Controller } $database = create_standalone_postgresql($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } $database->refresh(); $payload = [ @@ -1206,7 +1206,7 @@ class DatabasesController extends Controller } $database = create_standalone_mariadb($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } $database->refresh(); @@ -1264,7 +1264,7 @@ class DatabasesController extends Controller } $database = create_standalone_mysql($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } $database->refresh(); @@ -1320,7 +1320,7 @@ class DatabasesController extends Controller } $database = create_standalone_redis($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } $database->refresh(); @@ -1357,7 +1357,7 @@ class DatabasesController extends Controller removeUnnecessaryFieldsFromRequest($request); $database = create_standalone_dragonfly($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } return response()->json(serializeApiResponse([ @@ -1406,7 +1406,7 @@ class DatabasesController extends Controller } $database = create_standalone_keydb($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } $database->refresh(); @@ -1442,7 +1442,7 @@ class DatabasesController extends Controller removeUnnecessaryFieldsFromRequest($request); $database = create_standalone_clickhouse($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } $database->refresh(); @@ -1500,7 +1500,7 @@ class DatabasesController extends Controller } $database = create_standalone_mongodb($environment->id, $destination->uuid, $request->all()); if ($instantDeploy) { - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); } $database->refresh(); @@ -1593,7 +1593,7 @@ class DatabasesController extends Controller deleteVolumes: $request->query->get('delete_volumes', true), dockerCleanup: $request->query->get('docker_cleanup', true), deleteConnectedNetworks: $request->query->get('delete_connected_networks', true) - )->onQueue('high'); + ); return response()->json([ 'message' => 'Database deletion request queued.', @@ -1666,7 +1666,7 @@ class DatabasesController extends Controller if (str($database->status)->contains('running')) { return response()->json(['message' => 'Database is already running.'], 400); } - StartDatabase::dispatch($database)->onQueue('high'); + StartDatabase::dispatch($database); return response()->json( [ @@ -1742,7 +1742,7 @@ class DatabasesController extends Controller if (str($database->status)->contains('stopped') || str($database->status)->contains('exited')) { return response()->json(['message' => 'Database is already stopped.'], 400); } - StopDatabase::dispatch($database)->onQueue('high'); + StopDatabase::dispatch($database); return response()->json( [ @@ -1815,7 +1815,7 @@ class DatabasesController extends Controller if (! $database) { return response()->json(['message' => 'Database not found.'], 404); } - RestartDatabase::dispatch($database)->onQueue('high'); + RestartDatabase::dispatch($database); return response()->json( [ diff --git a/app/Http/Controllers/Api/DeployController.php b/app/Http/Controllers/Api/DeployController.php index 59b199d87..666dc55a5 100644 --- a/app/Http/Controllers/Api/DeployController.php +++ b/app/Http/Controllers/Api/DeployController.php @@ -307,7 +307,7 @@ class DeployController extends Controller break; default: // Database resource - StartDatabase::dispatch($resource)->onQueue('high'); + StartDatabase::dispatch($resource); $resource->update([ 'started_at' => now(), ]); diff --git a/app/Http/Controllers/Api/OtherController.php b/app/Http/Controllers/Api/OtherController.php index 062cc04e7..303e6535d 100644 --- a/app/Http/Controllers/Api/OtherController.php +++ b/app/Http/Controllers/Api/OtherController.php @@ -37,7 +37,7 @@ class OtherController extends Controller )] public function version(Request $request) { - return response(config('version')); + return response(config('constants.coolify.version')); } #[OA\Get( @@ -147,7 +147,7 @@ class OtherController extends Controller public function feedback(Request $request) { $content = $request->input('content'); - $webhook_url = config('coolify.feedback_discord_webhook'); + $webhook_url = config('constants.webhooks.feedback_discord_webhook'); if ($webhook_url) { Http::post($webhook_url, [ 'content' => $content, diff --git a/app/Http/Controllers/Api/ProjectController.php b/app/Http/Controllers/Api/ProjectController.php index 491179d5d..1d89c82ed 100644 --- a/app/Http/Controllers/Api/ProjectController.php +++ b/app/Http/Controllers/Api/ProjectController.php @@ -116,7 +116,7 @@ class ProjectController extends Controller responses: [ new OA\Response( response: 200, - description: 'Project details', + description: 'Environment details', content: new OA\JsonContent(ref: '#/components/schemas/Environment')), new OA\Response( response: 401, @@ -422,7 +422,7 @@ class ProjectController extends Controller if (! $project) { return response()->json(['message' => 'Project not found.'], 404); } - if ($project->resource_count() > 0) { + if (! $project->isEmpty()) { return response()->json(['message' => 'Project has resources, so it cannot be deleted.'], 400); } diff --git a/app/Http/Controllers/Api/SecurityController.php b/app/Http/Controllers/Api/SecurityController.php index bb474aed3..b7190ab1e 100644 --- a/app/Http/Controllers/Api/SecurityController.php +++ b/app/Http/Controllers/Api/SecurityController.php @@ -81,15 +81,8 @@ class SecurityController extends Controller new OA\Response( response: 200, description: 'Get all private keys.', - content: [ - new OA\MediaType( - mediaType: 'application/json', - schema: new OA\Schema( - type: 'array', - items: new OA\Items(ref: '#/components/schemas/PrivateKey') - ) - ), - ]), + content: new OA\JsonContent(ref: '#/components/schemas/PrivateKey') + ), new OA\Response( response: 401, ref: '#/components/responses/401', diff --git a/app/Http/Controllers/Api/ServersController.php b/app/Http/Controllers/Api/ServersController.php index 024ef35fa..8c13b1a01 100644 --- a/app/Http/Controllers/Api/ServersController.php +++ b/app/Http/Controllers/Api/ServersController.php @@ -426,6 +426,7 @@ class ServersController extends Controller 'private_key_uuid' => ['type' => 'string', 'example' => 'og888os', 'description' => 'The UUID of the private key.'], 'is_build_server' => ['type' => 'boolean', 'example' => false, 'description' => 'Is build server.'], 'instant_validate' => ['type' => 'boolean', 'example' => false, 'description' => 'Instant validate.'], + 'proxy_type' => ['type' => 'string', 'enum' => ['traefik', 'caddy', 'none'], 'example' => 'traefik', 'description' => 'The proxy type.'], ], ), ), @@ -461,7 +462,7 @@ class ServersController extends Controller )] public function create_server(Request $request) { - $allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate']; + $allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate', 'proxy_type']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { @@ -481,6 +482,7 @@ class ServersController extends Controller 'user' => 'string|nullable', 'is_build_server' => 'boolean|nullable', 'instant_validate' => 'boolean|nullable', + 'proxy_type' => 'string|nullable', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); @@ -512,6 +514,14 @@ class ServersController extends Controller if (is_null($request->instant_validate)) { $request->offsetSet('instant_validate', false); } + if ($request->proxy_type) { + $validProxyTypes = collect(ProxyTypes::cases())->map(function ($proxyType) { + return str($proxyType->value)->lower(); + }); + if (! $validProxyTypes->contains(str($request->proxy_type)->lower())) { + return response()->json(['message' => 'Invalid proxy type.'], 422); + } + } $privateKey = PrivateKey::whereTeamId($teamId)->whereUuid($request->private_key_uuid)->first(); if (! $privateKey) { return response()->json(['message' => 'Private key not found.'], 404); @@ -521,6 +531,8 @@ class ServersController extends Controller return response()->json(['message' => 'Server with this IP already exists.'], 400); } + $proxyType = $request->proxy_type ? str($request->proxy_type)->upper() : ProxyTypes::TRAEFIK->value; + $server = ModelsServer::create([ 'name' => $request->name, 'description' => $request->description, @@ -530,7 +542,7 @@ class ServersController extends Controller 'private_key_id' => $privateKey->id, 'team_id' => $teamId, 'proxy' => [ - 'type' => ProxyTypes::TRAEFIK->value, + 'type' => $proxyType, 'status' => ProxyStatus::EXITED->value, ], ]); @@ -538,7 +550,7 @@ class ServersController extends Controller 'is_build_server' => $request->is_build_server, ]); if ($request->instant_validate) { - ValidateServer::dispatch($server)->onQueue('high'); + ValidateServer::dispatch($server); } return response()->json([ @@ -555,6 +567,9 @@ class ServersController extends Controller ['bearerAuth' => []], ], tags: ['Servers'], + parameters: [ + new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Server UUID', schema: new OA\Schema(type: 'string')), + ], requestBody: new OA\RequestBody( required: true, description: 'Server updated.', @@ -571,6 +586,7 @@ class ServersController extends Controller 'private_key_uuid' => ['type' => 'string', 'description' => 'The UUID of the private key.'], 'is_build_server' => ['type' => 'boolean', 'description' => 'Is build server.'], 'instant_validate' => ['type' => 'boolean', 'description' => 'Instant validate.'], + 'proxy_type' => ['type' => 'string', 'enum' => ['traefik', 'caddy', 'none'], 'description' => 'The proxy type.'], ], ), ), @@ -583,8 +599,7 @@ class ServersController extends Controller new OA\MediaType( mediaType: 'application/json', schema: new OA\Schema( - type: 'array', - items: new OA\Items(ref: '#/components/schemas/Server') + ref: '#/components/schemas/Server' ) ), ]), @@ -604,7 +619,7 @@ class ServersController extends Controller )] public function update_server(Request $request) { - $allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate']; + $allowedFields = ['name', 'description', 'ip', 'port', 'user', 'private_key_uuid', 'is_build_server', 'instant_validate', 'proxy_type']; $teamId = getTeamIdFromToken(); if (is_null($teamId)) { @@ -624,6 +639,7 @@ class ServersController extends Controller 'user' => 'string|nullable', 'is_build_server' => 'boolean|nullable', 'instant_validate' => 'boolean|nullable', + 'proxy_type' => 'string|nullable', ]); $extraFields = array_diff(array_keys($request->all()), $allowedFields); @@ -644,6 +660,16 @@ class ServersController extends Controller if (! $server) { return response()->json(['message' => 'Server not found.'], 404); } + if ($request->proxy_type) { + $validProxyTypes = collect(ProxyTypes::cases())->map(function ($proxyType) { + return str($proxyType->value)->lower(); + }); + if ($validProxyTypes->contains(str($request->proxy_type)->lower())) { + $server->changeProxy($request->proxy_type, async: true); + } else { + return response()->json(['message' => 'Invalid proxy type.'], 422); + } + } $server->update($request->only(['name', 'description', 'ip', 'port', 'user'])); if ($request->is_build_server) { $server->settings()->update([ @@ -651,10 +677,12 @@ class ServersController extends Controller ]); } if ($request->instant_validate) { - ValidateServer::dispatch($server)->onQueue('high'); + ValidateServer::dispatch($server); } - return response()->json(serializeApiResponse($server))->setStatusCode(201); + return response()->json([ + 'uuid' => $server->uuid, + ])->setStatusCode(201); } #[OA\Delete( @@ -787,7 +815,7 @@ class ServersController extends Controller if (! $server) { return response()->json(['message' => 'Server not found.'], 404); } - ValidateServer::dispatch($server)->onQueue('high'); + ValidateServer::dispatch($server); return response()->json(['message' => 'Validation started.']); } diff --git a/app/Http/Controllers/Api/ServicesController.php b/app/Http/Controllers/Api/ServicesController.php index bdb5612ad..bf90322e2 100644 --- a/app/Http/Controllers/Api/ServicesController.php +++ b/app/Http/Controllers/Api/ServicesController.php @@ -342,7 +342,7 @@ class ServicesController extends Controller } $service->parse(isNew: true); if ($instantDeploy) { - StartService::dispatch($service)->onQueue('high'); + StartService::dispatch($service); } $domains = $service->applications()->get()->pluck('fqdn')->sort(); $domains = $domains->map(function ($domain) { @@ -487,7 +487,7 @@ class ServicesController extends Controller deleteVolumes: $request->query->get('delete_volumes', true), dockerCleanup: $request->query->get('docker_cleanup', true), deleteConnectedNetworks: $request->query->get('delete_connected_networks', true) - )->onQueue('high'); + ); return response()->json([ 'message' => 'Service deletion request queued.', @@ -1076,7 +1076,7 @@ class ServicesController extends Controller if (str($service->status())->contains('running')) { return response()->json(['message' => 'Service is already running.'], 400); } - StartService::dispatch($service)->onQueue('high'); + StartService::dispatch($service); return response()->json( [ @@ -1154,7 +1154,7 @@ class ServicesController extends Controller if (str($service->status())->contains('stopped') || str($service->status())->contains('exited')) { return response()->json(['message' => 'Service is already stopped.'], 400); } - StopService::dispatch($service)->onQueue('high'); + StopService::dispatch($service); return response()->json( [ @@ -1229,7 +1229,7 @@ class ServicesController extends Controller if (! $service) { return response()->json(['message' => 'Service not found.'], 404); } - RestartService::dispatch($service)->onQueue('high'); + RestartService::dispatch($service); return response()->json( [ diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index 718ba5ed4..9f1e4eeb8 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -110,13 +110,19 @@ class Controller extends BaseController return redirect()->route('login')->with('error', 'Invalid credentials.'); } - public function accept_invitation() + public function acceptInvitation() { $resetPassword = request()->query('reset-password'); $invitationUuid = request()->route('uuid'); + $invitation = TeamInvitation::whereUuid($invitationUuid)->firstOrFail(); $user = User::whereEmail($invitation->email)->firstOrFail(); + + if (Auth::id() !== $user->id) { + abort(400, 'You are not allowed to accept this invitation.'); + } $invitationValid = $invitation->isValid(); + if ($invitationValid) { if ($resetPassword) { $user->update([ @@ -131,14 +137,12 @@ class Controller extends BaseController } $user->teams()->attach($invitation->team->id, ['role' => $invitation->role]); $invitation->delete(); - if (Auth::id() !== $user->id) { - return redirect()->route('login'); - } + refreshSession($invitation->team); return redirect()->route('team.index'); } else { - abort(401); + abort(400, 'Invitation expired.'); } } diff --git a/app/Http/Controllers/Webhook/Gitlab.php b/app/Http/Controllers/Webhook/Gitlab.php index f56711bad..d8dcc0c3b 100644 --- a/app/Http/Controllers/Webhook/Gitlab.php +++ b/app/Http/Controllers/Webhook/Gitlab.php @@ -33,6 +33,7 @@ class Gitlab extends Controller return; } + $return_payloads = collect([]); $payload = $request->collect(); $headers = $request->headers->all(); @@ -48,6 +49,15 @@ class Gitlab extends Controller return response($return_payloads); } + if (empty($x_gitlab_token)) { + $return_payloads->push([ + 'status' => 'failed', + 'message' => 'Invalid signature.', + ]); + + return response($return_payloads); + } + if ($x_gitlab_event === 'push') { $branch = data_get($payload, 'ref'); $full_name = data_get($payload, 'project.path_with_namespace'); diff --git a/app/Http/Controllers/Webhook/Stripe.php b/app/Http/Controllers/Webhook/Stripe.php index 5d297b242..83ba16699 100644 --- a/app/Http/Controllers/Webhook/Stripe.php +++ b/app/Http/Controllers/Webhook/Stripe.php @@ -3,23 +3,26 @@ namespace App\Http\Controllers\Webhook; use App\Http\Controllers\Controller; -use App\Jobs\ServerLimitCheckJob; -use App\Jobs\SubscriptionInvoiceFailedJob; -use App\Jobs\SubscriptionTrialEndedJob; -use App\Jobs\SubscriptionTrialEndsSoonJob; -use App\Models\Subscription; -use App\Models\Team; +use App\Jobs\StripeProcessJob; use App\Models\Webhook; use Exception; use Illuminate\Http\Request; use Illuminate\Support\Facades\Storage; -use Illuminate\Support\Str; class Stripe extends Controller { + protected $webhook; + public function events(Request $request) { try { + $webhookSecret = config('subscription.stripe_webhook_secret'); + $signature = $request->header('Stripe-Signature'); + $event = \Stripe\Webhook::constructEvent( + $request->getContent(), + $signature, + $webhookSecret + ); if (app()->isDownForMaintenance()) { $epoch = now()->valueOf(); $data = [ @@ -35,276 +38,17 @@ class Stripe extends Controller $json = json_encode($data); Storage::disk('webhooks-during-maintenance')->put("{$epoch}_Stripe::events_stripe", $json); - return; + return response('Webhook received. Cool cool cool cool cool.', 200); } - $webhookSecret = config('subscription.stripe_webhook_secret'); - $signature = $request->header('Stripe-Signature'); - $excludedPlans = config('subscription.stripe_excluded_plans'); - $event = \Stripe\Webhook::constructEvent( - $request->getContent(), - $signature, - $webhookSecret - ); - $webhook = Webhook::create([ + $this->webhook = Webhook::create([ 'type' => 'stripe', 'payload' => $request->getContent(), ]); - $type = data_get($event, 'type'); - $data = data_get($event, 'data.object'); - switch ($type) { - case 'radar.early_fraud_warning.created': - $stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key')); - $id = data_get($data, 'id'); - $charge = data_get($data, 'charge'); - if ($charge) { - $stripe->refunds->create(['charge' => $charge]); - } - $pi = data_get($data, 'payment_intent'); - $piData = $stripe->paymentIntents->retrieve($pi, []); - $customerId = data_get($piData, 'customer'); - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if ($subscription) { - $subscriptionId = data_get($subscription, 'stripe_subscription_id'); - $stripe->subscriptions->cancel($subscriptionId, []); - $subscription->update([ - 'stripe_invoice_paid' => false, - ]); - send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}"); - } else { - send_internal_notification("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}"); + StripeProcessJob::dispatch($event); - return response("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}", 400); - } - break; - case 'checkout.session.completed': - $clientReferenceId = data_get($data, 'client_reference_id'); - if (is_null($clientReferenceId)) { - send_internal_notification('Checkout session completed without client reference id.'); - break; - } - $userId = Str::before($clientReferenceId, ':'); - $teamId = Str::after($clientReferenceId, ':'); - $subscriptionId = data_get($data, 'subscription'); - $customerId = data_get($data, 'customer'); - $team = Team::find($teamId); - $found = $team->members->where('id', $userId)->first(); - if (! $found->isAdmin()) { - send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}."); - - return response("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}.", 400); - } - $subscription = Subscription::where('team_id', $teamId)->first(); - if ($subscription) { - // send_internal_notification('Old subscription activated for team: '.$teamId); - $subscription->update([ - 'stripe_subscription_id' => $subscriptionId, - 'stripe_customer_id' => $customerId, - 'stripe_invoice_paid' => true, - ]); - } else { - // send_internal_notification('New subscription for team: '.$teamId); - Subscription::create([ - 'team_id' => $teamId, - 'stripe_subscription_id' => $subscriptionId, - 'stripe_customer_id' => $customerId, - 'stripe_invoice_paid' => true, - ]); - } - break; - case 'invoice.paid': - $customerId = data_get($data, 'customer'); - $planId = data_get($data, 'lines.data.0.plan.id'); - if (Str::contains($excludedPlans, $planId)) { - // send_internal_notification('Subscription excluded.'); - break; - } - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if ($subscription) { - $subscription->update([ - 'stripe_invoice_paid' => true, - ]); - } else { - return response("No subscription found for customer: {$customerId}", 400); - } - break; - case 'invoice.payment_failed': - $customerId = data_get($data, 'customer'); - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if (! $subscription) { - // send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId); - - return response('No subscription found in Coolify.'); - } - $team = data_get($subscription, 'team'); - if (! $team) { - // send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId); - - return response('No team found in Coolify.'); - } - if (! $subscription->stripe_invoice_paid) { - SubscriptionInvoiceFailedJob::dispatch($team); - // send_internal_notification('Invoice payment failed: '.$customerId); - } else { - // send_internal_notification('Invoice payment failed but already paid: '.$customerId); - } - break; - case 'payment_intent.payment_failed': - $customerId = data_get($data, 'customer'); - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if (! $subscription) { - // send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId); - - return response('No subscription found in Coolify.'); - } - if ($subscription->stripe_invoice_paid) { - // send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId); - - return; - } - send_internal_notification('Subscription payment failed for customer: '.$customerId); - break; - case 'customer.subscription.created': - $customerId = data_get($data, 'customer'); - $subscriptionId = data_get($data, 'id'); - $teamId = data_get($data, 'metadata.team_id'); - $userId = data_get($data, 'metadata.user_id'); - if (! $teamId || ! $userId) { - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if ($subscription) { - return response("Subscription already exists for customer: {$customerId}", 200); - } - - return response('No team id or user id found', 400); - } - $team = Team::find($teamId); - $found = $team->members->where('id', $userId)->first(); - if (! $found->isAdmin()) { - send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}."); - - return response("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}.", 400); - } - $subscription = Subscription::where('team_id', $teamId)->first(); - if ($subscription) { - return response("Subscription already exists for team: {$teamId}", 200); - } else { - Subscription::create([ - 'team_id' => $teamId, - 'stripe_subscription_id' => $subscriptionId, - 'stripe_customer_id' => $customerId, - 'stripe_invoice_paid' => false, - ]); - - return response('Subscription created'); - } - case 'customer.subscription.updated': - $teamId = data_get($data, 'metadata.team_id'); - $userId = data_get($data, 'metadata.user_id'); - $customerId = data_get($data, 'customer'); - $status = data_get($data, 'status'); - $subscriptionId = data_get($data, 'items.data.0.subscription'); - $planId = data_get($data, 'items.data.0.plan.id'); - if (Str::contains($excludedPlans, $planId)) { - // send_internal_notification('Subscription excluded.'); - break; - } - $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); - if (! $subscription) { - if ($status === 'incomplete_expired') { - return response('Subscription incomplete expired', 200); - } - if ($teamId) { - $subscription = Subscription::create([ - 'team_id' => $teamId, - 'stripe_subscription_id' => $subscriptionId, - 'stripe_customer_id' => $customerId, - 'stripe_invoice_paid' => false, - ]); - } else { - return response('No subscription and team id found', 400); - } - } - $cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end'); - $feedback = data_get($data, 'cancellation_details.feedback'); - $comment = data_get($data, 'cancellation_details.comment'); - $lookup_key = data_get($data, 'items.data.0.price.lookup_key'); - if (str($lookup_key)->contains('dynamic')) { - $quantity = data_get($data, 'items.data.0.quantity', 2); - $team = data_get($subscription, 'team'); - if ($team) { - $team->update([ - 'custom_server_limit' => $quantity, - ]); - } - ServerLimitCheckJob::dispatch($team); - } - $subscription->update([ - 'stripe_feedback' => $feedback, - 'stripe_comment' => $comment, - 'stripe_plan_id' => $planId, - 'stripe_cancel_at_period_end' => $cancelAtPeriodEnd, - ]); - if ($status === 'paused' || $status === 'incomplete_expired') { - $subscription->update([ - 'stripe_invoice_paid' => false, - ]); - } - if ($feedback) { - $reason = "Cancellation feedback for {$customerId}: '".$feedback."'"; - if ($comment) { - $reason .= ' with comment: \''.$comment."'"; - } - } - break; - case 'customer.subscription.deleted': - // End subscription - $customerId = data_get($data, 'customer'); - $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); - $team = data_get($subscription, 'team'); - if ($team) { - $team->trialEnded(); - } - $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, - ]); - // send_internal_notification('customer.subscription.deleted for customer: '.$customerId); - break; - case 'customer.subscription.trial_will_end': - // Not used for now - $customerId = data_get($data, 'customer'); - $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); - $team = data_get($subscription, 'team'); - if (! $team) { - return response('No team found for subscription: '.$subscription->id, 400); - } - SubscriptionTrialEndsSoonJob::dispatch($team); - break; - case 'customer.subscription.paused': - $customerId = data_get($data, 'customer'); - $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); - $team = data_get($subscription, 'team'); - if (! $team) { - return response('No team found for subscription: '.$subscription->id, 400); - } - $team->trialEnded(); - $subscription->update([ - 'stripe_trial_already_ended' => true, - 'stripe_invoice_paid' => false, - ]); - SubscriptionTrialEndedJob::dispatch($team); - // send_internal_notification('Subscription paused for customer: '.$customerId); - break; - default: - // Unhandled event type - } + return response('Webhook received. Cool cool cool cool cool.', 200); } catch (Exception $e) { - if ($type !== 'payment_intent.payment_failed') { - send_internal_notification("Subscription webhook ($type) failed: ".$e->getMessage()); - } - $webhook->update([ + $this->webhook->update([ 'status' => 'failed', 'failure_reason' => $e->getMessage(), ]); diff --git a/app/Jobs/ApplicationDeploymentJob.php b/app/Jobs/ApplicationDeploymentJob.php index 5ceed332a..6a66bb56d 100644 --- a/app/Jobs/ApplicationDeploymentJob.php +++ b/app/Jobs/ApplicationDeploymentJob.php @@ -166,6 +166,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue public function __construct(int $application_deployment_queue_id) { + $this->onQueue('high'); + $this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id); $this->application = Application::find($this->application_deployment_queue->application_id); $this->build_pack = data_get($this->application, 'build_pack'); @@ -225,6 +227,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue } } + public function tags(): array + { + return ['server:'.gethostname()]; + } + public function handle(): void { $this->application_deployment_queue->update([ @@ -344,8 +351,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private function post_deployment() { if ($this->server->isProxyShouldRun()) { - GetContainersStatus::dispatch($this->server)->onQueue('high'); - // dispatch(new ContainerStatusJob($this->server)); + GetContainersStatus::dispatch($this->server); } $this->next(ApplicationDeploymentStatus::FINISHED->value); if ($this->pull_request_id !== 0) { @@ -457,7 +463,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue $composeFile = $this->application->parse(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id')); $this->save_environment_variables(); if (! is_null($this->env_filename)) { - $services = collect($composeFile['services']); + $services = collect(data_get($composeFile, 'services', [])); $services = $services->map(function ($service, $name) { $service['env_file'] = [$this->env_filename]; @@ -1318,7 +1324,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue private function prepare_builder_image() { $settings = instanceSettings(); - $helperImage = config('coolify.helper_image'); + $helperImage = config('constants.coolify.helper_image'); $helperImage = "{$helperImage}:{$settings->helper_version}"; // Get user home directory $this->serverUserHomeDir = instant_remote_process(['echo $HOME'], $this->server); @@ -1990,22 +1996,11 @@ COPY . . RUN rm -f /usr/share/nginx/html/nginx.conf RUN rm -f /usr/share/nginx/html/Dockerfile COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); - $nginx_config = base64_encode('server { - listen 80; - listen [::]:80; - server_name localhost; - - location / { - root /usr/share/nginx/html; - index index.html; - try_files $uri $uri.html $uri/index.html $uri/ /index.html =404; + if (str($this->application->custom_nginx_configuration)->isNotEmpty()) { + $nginx_config = base64_encode($this->application->custom_nginx_configuration); + } else { + $nginx_config = base64_encode(defaultNginxConfiguration()); } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } - }'); } else { if ($this->application->build_pack === 'nixpacks') { $this->nixpacks_plan = base64_encode($this->nixpacks_plan); @@ -2068,23 +2063,11 @@ WORKDIR /usr/share/nginx/html/ LABEL coolify.deploymentId={$this->deployment_uuid} COPY --from=$this->build_image_name /app/{$this->application->publish_directory} . COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); - - $nginx_config = base64_encode('server { - listen 80; - listen [::]:80; - server_name localhost; - - location / { - root /usr/share/nginx/html; - index index.html; - try_files $uri $uri.html $uri/index.html $uri/ /index.html =404; + if (str($this->application->custom_nginx_configuration)->isNotEmpty()) { + $nginx_config = base64_encode($this->application->custom_nginx_configuration); + } else { + $nginx_config = base64_encode(defaultNginxConfiguration()); } - - error_page 500 502 503 504 /50x.html; - location = /50x.html { - root /usr/share/nginx/html; - } - }'); } $build_command = "docker build {$this->addHosts} --network host -f {$this->workdir}/Dockerfile {$this->build_args} --progress plain -t {$this->production_image_name} {$this->workdir}"; $base64_build_command = base64_encode($build_command); diff --git a/app/Jobs/ApplicationPullRequestUpdateJob.php b/app/Jobs/ApplicationPullRequestUpdateJob.php index 2eefc4dd2..ef8e6efb6 100755 --- a/app/Jobs/ApplicationPullRequestUpdateJob.php +++ b/app/Jobs/ApplicationPullRequestUpdateJob.php @@ -25,7 +25,9 @@ class ApplicationPullRequestUpdateJob implements ShouldBeEncrypted, ShouldQueue public ApplicationPreview $preview, public ProcessStatus $status, public ?string $deployment_uuid = null - ) {} + ) { + $this->onQueue('high'); + } public function handle() { diff --git a/app/Jobs/CheckForUpdatesJob.php b/app/Jobs/CheckForUpdatesJob.php index f2348118a..1d3a345e1 100644 --- a/app/Jobs/CheckForUpdatesJob.php +++ b/app/Jobs/CheckForUpdatesJob.php @@ -27,7 +27,7 @@ class CheckForUpdatesJob implements ShouldBeEncrypted, ShouldQueue $versions = $response->json(); $latest_version = data_get($versions, 'coolify.v4.version'); - $current_version = config('version'); + $current_version = config('constants.coolify.version'); if (version_compare($latest_version, $current_version, '>')) { // New version available diff --git a/app/Jobs/CleanupInstanceStuffsJob.php b/app/Jobs/CleanupInstanceStuffsJob.php index acbe82338..84f14ed02 100644 --- a/app/Jobs/CleanupInstanceStuffsJob.php +++ b/app/Jobs/CleanupInstanceStuffsJob.php @@ -3,14 +3,15 @@ namespace App\Jobs; use App\Models\TeamInvitation; -use App\Models\Waitlist; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldBeUnique; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; +use Illuminate\Support\Facades\Log; class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, ShouldQueue { @@ -18,34 +19,21 @@ class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, Sho public function __construct() {} - // public function uniqueId(): string - // { - // return $this->container_name; - // } + public function middleware(): array + { + return [(new WithoutOverlapping('cleanup-instance-stuffs'))->dontRelease()]; + } public function handle(): void { try { - // $this->cleanup_waitlist(); + $this->cleanupInvitationLink(); } catch (\Throwable $e) { - send_internal_notification('CleanupInstanceStuffsJob failed with error: '.$e->getMessage()); - } - try { - $this->cleanup_invitation_link(); - } catch (\Throwable $e) { - send_internal_notification('CleanupInstanceStuffsJob failed with error: '.$e->getMessage()); + Log::error('CleanupInstanceStuffsJob failed with error: '.$e->getMessage()); } } - private function cleanup_waitlist() - { - $waitlist = Waitlist::whereVerified(false)->where('created_at', '<', now()->subMinutes(config('constants.waitlist.expiration')))->get(); - foreach ($waitlist as $item) { - $item->delete(); - } - } - - private function cleanup_invitation_link() + private function cleanupInvitationLink() { $invitation = TeamInvitation::all(); foreach ($invitation as $item) { diff --git a/app/Jobs/CoolifyTask.php b/app/Jobs/CoolifyTask.php index c3692c30b..49a5ba8dd 100755 --- a/app/Jobs/CoolifyTask.php +++ b/app/Jobs/CoolifyTask.php @@ -23,7 +23,10 @@ class CoolifyTask implements ShouldBeEncrypted, ShouldQueue public bool $ignore_errors, public $call_event_on_finish, public $call_event_data, - ) {} + ) { + + $this->onQueue('high'); + } /** * Execute the job. diff --git a/app/Jobs/DatabaseBackupJob.php b/app/Jobs/DatabaseBackupJob.php index fcfe2fe3d..89674b255 100644 --- a/app/Jobs/DatabaseBackupJob.php +++ b/app/Jobs/DatabaseBackupJob.php @@ -60,12 +60,15 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue public function __construct($backup) { + $this->onQueue('high'); $this->backup = $backup; } public function handle(): void { try { + $databasesToBackup = null; + $this->team = Team::find($this->backup->team_id); if (! $this->team) { $this->backup->delete(); @@ -197,8 +200,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue $databaseType = $this->database->type(); $databasesToBackup = data_get($this->backup, 'databases_to_backup'); } - - if (is_null($databasesToBackup)) { + if (blank($databasesToBackup)) { if (str($databaseType)->contains('postgres')) { $databasesToBackup = [$this->database->postgres_db]; } elseif (str($databaseType)->contains('mongodb')) { @@ -319,12 +321,10 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue 'filename' => null, ]); } - send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage()); $this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output, $database)); } } } catch (\Throwable $e) { - send_internal_notification('DatabaseBackupJob failed with: '.$e->getMessage()); throw $e; } finally { if ($this->team) { @@ -524,7 +524,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue private function getFullImageName(): string { $settings = instanceSettings(); - $helperImage = config('coolify.helper_image'); + $helperImage = config('constants.coolify.helper_image'); $latestVersion = $settings->helper_version; return "{$helperImage}:{$latestVersion}"; diff --git a/app/Jobs/DeleteResourceJob.php b/app/Jobs/DeleteResourceJob.php index 2442d5b06..8b9228e5f 100644 --- a/app/Jobs/DeleteResourceJob.php +++ b/app/Jobs/DeleteResourceJob.php @@ -35,7 +35,9 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue public bool $deleteVolumes = true, public bool $dockerCleanup = true, public bool $deleteConnectedNetworks = true - ) {} + ) { + $this->onQueue('high'); + } public function handle() { @@ -87,7 +89,6 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue $this->resource?->delete_connected_networks($this->resource->uuid); } } catch (\Throwable $e) { - send_internal_notification('ContainerStoppingJob failed with: '.$e->getMessage()); throw $e; } finally { $this->resource->forceDelete(); diff --git a/app/Jobs/DockerCleanupJob.php b/app/Jobs/DockerCleanupJob.php index 900bae99c..80542e03b 100644 --- a/app/Jobs/DockerCleanupJob.php +++ b/app/Jobs/DockerCleanupJob.php @@ -10,6 +10,7 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; +use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\SerializesModels; use Illuminate\Support\Facades\Log; @@ -23,6 +24,11 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue public ?string $usageBefore = null; + public function middleware(): array + { + return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; + } + public function __construct(public Server $server, public bool $manualCleanup = false) {} public function handle(): void diff --git a/app/Jobs/PullHelperImageJob.php b/app/Jobs/PullHelperImageJob.php index a92e44c6b..b92886d38 100644 --- a/app/Jobs/PullHelperImageJob.php +++ b/app/Jobs/PullHelperImageJob.php @@ -16,11 +16,14 @@ class PullHelperImageJob implements ShouldBeEncrypted, ShouldQueue public $timeout = 1000; - public function __construct(public Server $server) {} + public function __construct(public Server $server) + { + $this->onQueue('high'); + } public function handle(): void { - $helperImage = config('coolify.helper_image'); + $helperImage = config('constants.coolify.helper_image'); $latest_version = instanceSettings()->helper_version; instant_remote_process(["docker pull -q {$helperImage}:{$latest_version}"], $this->server, false); } diff --git a/app/Jobs/PullTemplatesFromCDN.php b/app/Jobs/PullTemplatesFromCDN.php index bde5e6c7a..45c536e06 100644 --- a/app/Jobs/PullTemplatesFromCDN.php +++ b/app/Jobs/PullTemplatesFromCDN.php @@ -17,7 +17,10 @@ class PullTemplatesFromCDN implements ShouldBeEncrypted, ShouldQueue public $timeout = 10; - public function __construct() {} + public function __construct() + { + $this->onQueue('high'); + } public function handle(): void { diff --git a/app/Jobs/PushServerUpdateJob.php b/app/Jobs/PushServerUpdateJob.php index 9822ca071..24f8d1e6b 100644 --- a/app/Jobs/PushServerUpdateJob.php +++ b/app/Jobs/PushServerUpdateJob.php @@ -360,7 +360,7 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue private function checkLogDrainContainer() { if ($this->server->isLogDrainEnabled() && $this->foundLogDrainContainer === false) { - StartLogDrain::dispatch($this->server)->onQueue('high'); + StartLogDrain::dispatch($this->server); } } } diff --git a/app/Jobs/ScheduledTaskJob.php b/app/Jobs/ScheduledTaskJob.php index 827641e8c..00575e187 100644 --- a/app/Jobs/ScheduledTaskJob.php +++ b/app/Jobs/ScheduledTaskJob.php @@ -2,6 +2,7 @@ namespace App\Jobs; +use App\Events\ScheduledTaskDone; use App\Models\Application; use App\Models\ScheduledTask; use App\Models\ScheduledTaskExecution; @@ -19,7 +20,7 @@ class ScheduledTaskJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public ?Team $team = null; + public Team $team; public Server $server; @@ -39,6 +40,8 @@ class ScheduledTaskJob implements ShouldQueue public function __construct($task) { + $this->onQueue('high'); + $this->task = $task; if ($service = $task->service()->first()) { $this->resource = $service; @@ -47,7 +50,7 @@ class ScheduledTaskJob implements ShouldQueue } else { throw new \RuntimeException('ScheduledTaskJob failed: No resource found.'); } - $this->team = Team::find($task->team_id); + $this->team = Team::findOrFail($task->team_id); $this->server_timezone = $this->getServerTimezone(); } @@ -125,6 +128,7 @@ class ScheduledTaskJob implements ShouldQueue // send_internal_notification('ScheduledTaskJob failed with: ' . $e->getMessage()); throw $e; } finally { + ScheduledTaskDone::dispatch($this->team->id); } } } diff --git a/app/Jobs/SendMessageToDiscordJob.php b/app/Jobs/SendMessageToDiscordJob.php index 5b406f50f..99aeaeea2 100644 --- a/app/Jobs/SendMessageToDiscordJob.php +++ b/app/Jobs/SendMessageToDiscordJob.php @@ -32,7 +32,9 @@ class SendMessageToDiscordJob implements ShouldBeEncrypted, ShouldQueue public function __construct( public DiscordMessage $message, public string $webhookUrl - ) {} + ) { + $this->onQueue('high'); + } /** * Execute the job. diff --git a/app/Jobs/SendMessageToTelegramJob.php b/app/Jobs/SendMessageToTelegramJob.php index bf52b782f..85f4fc934 100644 --- a/app/Jobs/SendMessageToTelegramJob.php +++ b/app/Jobs/SendMessageToTelegramJob.php @@ -33,7 +33,9 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue public string $token, public string $chatId, public ?string $topicId = null, - ) {} + ) { + $this->onQueue('high'); + } /** * Execute the job. @@ -70,7 +72,7 @@ class SendMessageToTelegramJob implements ShouldBeEncrypted, ShouldQueue } $response = Http::post($url, $payload); if ($response->failed()) { - throw new \Exception('Telegram notification failed with '.$response->status().' status code.'.$response->body()); + throw new \RuntimeException('Telegram notification failed with '.$response->status().' status code.'.$response->body()); } } } diff --git a/app/Jobs/ServerCheckJob.php b/app/Jobs/ServerCheckJob.php index c584f493d..49d8dfe08 100644 --- a/app/Jobs/ServerCheckJob.php +++ b/app/Jobs/ServerCheckJob.php @@ -28,10 +28,15 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue public function middleware(): array { - return [(new WithoutOverlapping($this->server->id))->dontRelease()]; + return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; } - public function __construct(public Server $server) {} + public function __construct(public Server $server) + { + if (isDev()) { + $this->handle(); + } + } public function handle() { @@ -94,10 +99,10 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue if ($foundLogDrainContainer) { $status = data_get($foundLogDrainContainer, 'State.Status'); if ($status !== 'running') { - StartLogDrain::dispatch($this->server)->onQueue('high'); + StartLogDrain::dispatch($this->server); } } else { - StartLogDrain::dispatch($this->server)->onQueue('high'); + StartLogDrain::dispatch($this->server); } } } diff --git a/app/Jobs/ServerCheckNewJob.php b/app/Jobs/ServerCheckNewJob.php index 9ce52759c..3e8e60a31 100644 --- a/app/Jobs/ServerCheckNewJob.php +++ b/app/Jobs/ServerCheckNewJob.php @@ -2,6 +2,7 @@ namespace App\Jobs; +use App\Actions\Server\ResourcesCheck; use App\Actions\Server\ServerCheck; use App\Models\Server; use Illuminate\Bus\Queueable; @@ -25,6 +26,7 @@ class ServerCheckNewJob implements ShouldBeEncrypted, ShouldQueue { try { ServerCheck::run($this->server); + ResourcesCheck::dispatch($this->server); } catch (\Throwable $e) { return handleError($e); } diff --git a/app/Jobs/ServerFilesFromServerJob.php b/app/Jobs/ServerFilesFromServerJob.php index 769dfc004..58455df2f 100644 --- a/app/Jobs/ServerFilesFromServerJob.php +++ b/app/Jobs/ServerFilesFromServerJob.php @@ -16,7 +16,10 @@ class ServerFilesFromServerJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public ServiceApplication|ServiceDatabase|Application $resource) {} + public function __construct(public ServiceApplication|ServiceDatabase|Application $resource) + { + $this->onQueue('high'); + } public function handle() { diff --git a/app/Jobs/ServerStorageCheckJob.php b/app/Jobs/ServerStorageCheckJob.php index 0723ffcee..9a8d86be1 100644 --- a/app/Jobs/ServerStorageCheckJob.php +++ b/app/Jobs/ServerStorageCheckJob.php @@ -25,7 +25,7 @@ class ServerStorageCheckJob implements ShouldBeEncrypted, ShouldQueue return isDev() ? 1 : 3; } - public function __construct(public Server $server, public ?int $percentage = null) {} + public function __construct(public Server $server, public int|string|null $percentage = null) {} public function handle() { diff --git a/app/Jobs/ServerStorageSaveJob.php b/app/Jobs/ServerStorageSaveJob.php index 526cd5375..17a293f94 100644 --- a/app/Jobs/ServerStorageSaveJob.php +++ b/app/Jobs/ServerStorageSaveJob.php @@ -14,7 +14,10 @@ class ServerStorageSaveJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(public LocalFileVolume $localFileVolume) {} + public function __construct(public LocalFileVolume $localFileVolume) + { + $this->onQueue('high'); + } public function handle() { diff --git a/app/Jobs/StripeProcessJob.php b/app/Jobs/StripeProcessJob.php new file mode 100644 index 000000000..00c9b6d18 --- /dev/null +++ b/app/Jobs/StripeProcessJob.php @@ -0,0 +1,246 @@ +onQueue('high'); + } + + public function handle(): void + { + try { + $excludedPlans = config('subscription.stripe_excluded_plans'); + + $type = data_get($this->event, 'type'); + $this->type = $type; + $data = data_get($this->event, 'data.object'); + switch ($type) { + case 'radar.early_fraud_warning.created': + $stripe = new \Stripe\StripeClient(config('subscription.stripe_api_key')); + $id = data_get($data, 'id'); + $charge = data_get($data, 'charge'); + if ($charge) { + $stripe->refunds->create(['charge' => $charge]); + } + $pi = data_get($data, 'payment_intent'); + $piData = $stripe->paymentIntents->retrieve($pi, []); + $customerId = data_get($piData, 'customer'); + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if ($subscription) { + $subscriptionId = data_get($subscription, 'stripe_subscription_id'); + $stripe->subscriptions->cancel($subscriptionId, []); + $subscription->update([ + 'stripe_invoice_paid' => false, + ]); + send_internal_notification("Early fraud warning created Refunded, subscription canceled. Charge: {$charge}, id: {$id}, pi: {$pi}"); + } else { + send_internal_notification("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}"); + throw new \RuntimeException("Early fraud warning: subscription not found. Charge: {$charge}, id: {$id}, pi: {$pi}"); + } + break; + case 'checkout.session.completed': + $clientReferenceId = data_get($data, 'client_reference_id'); + if (is_null($clientReferenceId)) { + send_internal_notification('Checkout session completed without client reference id.'); + break; + } + $userId = Str::before($clientReferenceId, ':'); + $teamId = Str::after($clientReferenceId, ':'); + $subscriptionId = data_get($data, 'subscription'); + $customerId = data_get($data, 'customer'); + $team = Team::find($teamId); + $found = $team->members->where('id', $userId)->first(); + if (! $found->isAdmin()) { + send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}."); + throw new \RuntimeException("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}, subscriptionid: {$subscriptionId}."); + } + $subscription = Subscription::where('team_id', $teamId)->first(); + if ($subscription) { + send_internal_notification('Old subscription activated for team: '.$teamId); + $subscription->update([ + 'stripe_subscription_id' => $subscriptionId, + 'stripe_customer_id' => $customerId, + 'stripe_invoice_paid' => true, + ]); + } else { + send_internal_notification('New subscription for team: '.$teamId); + Subscription::create([ + 'team_id' => $teamId, + 'stripe_subscription_id' => $subscriptionId, + 'stripe_customer_id' => $customerId, + 'stripe_invoice_paid' => true, + ]); + } + break; + case 'invoice.paid': + $customerId = data_get($data, 'customer'); + $planId = data_get($data, 'lines.data.0.plan.id'); + if (Str::contains($excludedPlans, $planId)) { + send_internal_notification('Subscription excluded.'); + break; + } + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if ($subscription) { + $subscription->update([ + 'stripe_invoice_paid' => true, + ]); + } else { + throw new \RuntimeException("No subscription found for customer: {$customerId}"); + } + break; + case 'invoice.payment_failed': + $customerId = data_get($data, 'customer'); + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if (! $subscription) { + send_internal_notification('invoice.payment_failed failed but no subscription found in Coolify for customer: '.$customerId); + throw new \RuntimeException("No subscription found for customer: {$customerId}"); + } + $team = data_get($subscription, 'team'); + if (! $team) { + send_internal_notification('invoice.payment_failed failed but no team found in Coolify for customer: '.$customerId); + throw new \RuntimeException("No team found in Coolify for customer: {$customerId}"); + } + if (! $subscription->stripe_invoice_paid) { + SubscriptionInvoiceFailedJob::dispatch($team); + send_internal_notification('Invoice payment failed: '.$customerId); + } else { + send_internal_notification('Invoice payment failed but already paid: '.$customerId); + } + break; + case 'payment_intent.payment_failed': + $customerId = data_get($data, 'customer'); + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if (! $subscription) { + send_internal_notification('payment_intent.payment_failed, no subscription found in Coolify for customer: '.$customerId); + throw new \RuntimeException("No subscription found in Coolify for customer: {$customerId}"); + } + if ($subscription->stripe_invoice_paid) { + send_internal_notification('payment_intent.payment_failed but invoice is active for customer: '.$customerId); + + return; + } + send_internal_notification('Subscription payment failed for customer: '.$customerId); + break; + case 'customer.subscription.created': + $customerId = data_get($data, 'customer'); + $subscriptionId = data_get($data, 'id'); + $teamId = data_get($data, 'metadata.team_id'); + $userId = data_get($data, 'metadata.user_id'); + if (! $teamId || ! $userId) { + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if ($subscription) { + throw new \RuntimeException("Subscription already exists for customer: {$customerId}"); + } + throw new \RuntimeException('No team id or user id found'); + } + $team = Team::find($teamId); + $found = $team->members->where('id', $userId)->first(); + if (! $found->isAdmin()) { + send_internal_notification("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}."); + throw new \RuntimeException("User {$userId} is not an admin or owner of team {$team->id}, customerid: {$customerId}."); + } + $subscription = Subscription::where('team_id', $teamId)->first(); + if ($subscription) { + send_internal_notification("Subscription already exists for team: {$teamId}"); + throw new \RuntimeException("Subscription already exists for team: {$teamId}"); + } else { + Subscription::create([ + 'team_id' => $teamId, + 'stripe_subscription_id' => $subscriptionId, + 'stripe_customer_id' => $customerId, + 'stripe_invoice_paid' => false, + ]); + } + case 'customer.subscription.updated': + $teamId = data_get($data, 'metadata.team_id'); + $userId = data_get($data, 'metadata.user_id'); + $customerId = data_get($data, 'customer'); + $status = data_get($data, 'status'); + $subscriptionId = data_get($data, 'items.data.0.subscription'); + $planId = data_get($data, 'items.data.0.plan.id'); + if (Str::contains($excludedPlans, $planId)) { + send_internal_notification('Subscription excluded.'); + break; + } + $subscription = Subscription::where('stripe_customer_id', $customerId)->first(); + if (! $subscription) { + if ($status === 'incomplete_expired') { + send_internal_notification('Subscription incomplete expired'); + throw new \RuntimeException('Subscription incomplete expired'); + } + if ($teamId) { + $subscription = Subscription::create([ + 'team_id' => $teamId, + 'stripe_subscription_id' => $subscriptionId, + 'stripe_customer_id' => $customerId, + 'stripe_invoice_paid' => false, + ]); + } else { + send_internal_notification('No subscription and team id found'); + throw new \RuntimeException('No subscription and team id found'); + } + } + $cancelAtPeriodEnd = data_get($data, 'cancel_at_period_end'); + $feedback = data_get($data, 'cancellation_details.feedback'); + $comment = data_get($data, 'cancellation_details.comment'); + $lookup_key = data_get($data, 'items.data.0.price.lookup_key'); + if (str($lookup_key)->contains('dynamic')) { + $quantity = data_get($data, 'items.data.0.quantity', 2); + $team = data_get($subscription, 'team'); + if ($team) { + $team->update([ + 'custom_server_limit' => $quantity, + ]); + } + ServerLimitCheckJob::dispatch($team); + } + $subscription->update([ + 'stripe_feedback' => $feedback, + 'stripe_comment' => $comment, + 'stripe_plan_id' => $planId, + 'stripe_cancel_at_period_end' => $cancelAtPeriodEnd, + ]); + if ($status === 'paused' || $status === 'incomplete_expired') { + $subscription->update([ + 'stripe_invoice_paid' => false, + ]); + } + if ($feedback) { + $reason = "Cancellation feedback for {$customerId}: '".$feedback."'"; + if ($comment) { + $reason .= ' with comment: \''.$comment."'"; + } + } + break; + case 'customer.subscription.deleted': + // End subscription + $customerId = data_get($data, 'customer'); + $subscription = Subscription::where('stripe_customer_id', $customerId)->firstOrFail(); + $team = data_get($subscription, 'team'); + $team?->subscriptionEnded(); + break; + default: + throw new \RuntimeException("Unhandled event type: {$type}"); + } + } catch (\Exception $e) { + send_internal_notification('StripeProcessJob error: '.$e->getMessage()); + } + } +} diff --git a/app/Jobs/SubscriptionInvoiceFailedJob.php b/app/Jobs/SubscriptionInvoiceFailedJob.php index aabeecef5..dc511f445 100755 --- a/app/Jobs/SubscriptionInvoiceFailedJob.php +++ b/app/Jobs/SubscriptionInvoiceFailedJob.php @@ -15,7 +15,10 @@ class SubscriptionInvoiceFailedJob implements ShouldBeEncrypted, ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; - public function __construct(protected Team $team) {} + public function __construct(protected Team $team) + { + $this->onQueue('high'); + } public function handle() { diff --git a/app/Jobs/SubscriptionTrialEndedJob.php b/app/Jobs/SubscriptionTrialEndedJob.php deleted file mode 100755 index 88a5e06be..000000000 --- a/app/Jobs/SubscriptionTrialEndedJob.php +++ /dev/null @@ -1,42 +0,0 @@ -team); - $mail = new MailMessage; - $mail->subject('Action required: You trial in Coolify Cloud ended.'); - $mail->view('emails.trial-ended', [ - 'stripeCustomerPortal' => $session->url, - ]); - $this->team->members()->each(function ($member) use ($mail) { - if ($member->isAdmin()) { - send_user_an_email($mail, $member->email); - send_internal_notification('Trial reminder email sent to '.$member->email); - } - }); - } catch (\Throwable $e) { - send_internal_notification('SubscriptionTrialEndsSoonJob failed with: '.$e->getMessage()); - throw $e; - } - } -} diff --git a/app/Jobs/SubscriptionTrialEndsSoonJob.php b/app/Jobs/SubscriptionTrialEndsSoonJob.php deleted file mode 100755 index 2a76a1097..000000000 --- a/app/Jobs/SubscriptionTrialEndsSoonJob.php +++ /dev/null @@ -1,42 +0,0 @@ -team); - $mail = new MailMessage; - $mail->subject('You trial in Coolify Cloud ends soon.'); - $mail->view('emails.trial-ends-soon', [ - 'stripeCustomerPortal' => $session->url, - ]); - $this->team->members()->each(function ($member) use ($mail) { - if ($member->isAdmin()) { - send_user_an_email($mail, $member->email); - send_internal_notification('Trial reminder email sent to '.$member->email); - } - }); - } catch (\Throwable $e) { - send_internal_notification('SubscriptionTrialEndsSoonJob failed with: '.$e->getMessage()); - throw $e; - } - } -} diff --git a/app/Jobs/UpdateCoolifyJob.php b/app/Jobs/UpdateCoolifyJob.php index 1e5197b6f..f0e43cbc0 100644 --- a/app/Jobs/UpdateCoolifyJob.php +++ b/app/Jobs/UpdateCoolifyJob.php @@ -18,6 +18,11 @@ class UpdateCoolifyJob implements ShouldBeEncrypted, ShouldQueue public $timeout = 600; + public function __construct() + { + $this->onQueue('high'); + } + public function handle(): void { try { diff --git a/app/Livewire/Admin/Index.php b/app/Livewire/Admin/Index.php index 2579c3db2..359db6329 100644 --- a/app/Livewire/Admin/Index.php +++ b/app/Livewire/Admin/Index.php @@ -2,8 +2,8 @@ namespace App\Livewire\Admin; +use App\Models\Team; use App\Models\User; -use Illuminate\Container\Attributes\Auth as AttributesAuth; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Cache; @@ -43,17 +43,13 @@ class Index extends Component public function getSubscribers() { - $this->inactiveSubscribers = User::whereDoesntHave('teams', function ($query) { - $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null); - })->count(); - $this->activeSubscribers = User::whereHas('teams', function ($query) { - $query->whereRelation('subscription', 'stripe_subscription_id', '!=', null); - })->count(); + $this->inactiveSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', false)->count(); + $this->activeSubscribers = Team::whereRelation('subscription', 'stripe_invoice_paid', true)->count(); } public function switchUser(int $user_id) { - if (AttributesAuth::id() !== 0) { + if (Auth::id() !== 0) { return redirect()->route('dashboard'); } $user = User::find($user_id); diff --git a/app/Livewire/Boarding/Index.php b/app/Livewire/Boarding/Index.php index c9c3092b3..b6c799c4e 100644 --- a/app/Livewire/Boarding/Index.php +++ b/app/Livewire/Boarding/Index.php @@ -66,11 +66,15 @@ class Index extends Component public bool $serverReachable = true; + public ?string $minDockerVersion = null; + public function mount() { if (auth()->user()?->isMember() && auth()->user()->currentTeam()->show_boarding === true) { return redirect()->route('dashboard'); } + + $this->minDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.'); $this->privateKeyName = generate_random_name(); $this->remoteServerName = generate_random_name(); if (isDev()) { diff --git a/app/Livewire/Destination/New/Docker.php b/app/Livewire/Destination/New/Docker.php index 8cf58ec41..337f1d067 100644 --- a/app/Livewire/Destination/New/Docker.php +++ b/app/Livewire/Destination/New/Docker.php @@ -35,9 +35,11 @@ class Docker extends Component $this->network = new Cuid2; $this->servers = Server::isUsable()->get(); if ($server_id) { - $this->selectedServer = $this->servers->find($server_id); + $this->selectedServer = $this->servers->find($server_id) ?: $this->servers->first(); + $this->serverId = $this->selectedServer->id; } else { $this->selectedServer = $this->servers->first(); + $this->serverId = $this->selectedServer->id; } $this->generateName(); } diff --git a/app/Livewire/Dev/Compose.php b/app/Livewire/Dev/Compose.php deleted file mode 100644 index a5cd53fc2..000000000 --- a/app/Livewire/Dev/Compose.php +++ /dev/null @@ -1,37 +0,0 @@ -services = get_service_templates(); - } - - public function setService(string $selected) - { - $this->base64 = data_get($this->services, $selected.'.compose'); - if ($this->base64) { - $this->compose = base64_decode($this->base64); - } - } - - public function updatedCompose($value) - { - $this->base64 = base64_encode($value); - } - - public function render() - { - return view('livewire.dev.compose'); - } -} diff --git a/app/Livewire/NavbarDeleteTeam.php b/app/Livewire/NavbarDeleteTeam.php index cc5d78f60..e97cceb0d 100644 --- a/app/Livewire/NavbarDeleteTeam.php +++ b/app/Livewire/NavbarDeleteTeam.php @@ -3,7 +3,6 @@ namespace App\Livewire; use App\Models\InstanceSettings; -use Illuminate\Container\Attributes\Auth as AttributesAuth; use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Hash; @@ -32,7 +31,7 @@ class NavbarDeleteTeam extends Component $currentTeam->delete(); $currentTeam->members->each(function ($user) use ($currentTeam) { - if ($user->id === AttributesAuth::id()) { + if ($user->id === Auth::id()) { return; } $user->teams()->detach($currentTeam); diff --git a/app/Livewire/Notifications/Email.php b/app/Livewire/Notifications/Email.php index b49638b48..fcedf1305 100644 --- a/app/Livewire/Notifications/Email.php +++ b/app/Livewire/Notifications/Email.php @@ -50,22 +50,22 @@ class Email extends Component public ?int $smtpTimeout = null; #[Validate(['boolean'])] - public bool $smtpNotificationsTest; + public bool $smtpNotificationsTest = false; #[Validate(['boolean'])] - public bool $smtpNotificationsDeployments; + public bool $smtpNotificationsDeployments = false; #[Validate(['boolean'])] - public bool $smtpNotificationsStatusChanges; + public bool $smtpNotificationsStatusChanges = false; #[Validate(['boolean'])] - public bool $smtpNotificationsDatabaseBackups; + public bool $smtpNotificationsDatabaseBackups = false; #[Validate(['boolean'])] - public bool $smtpNotificationsScheduledTasks; + public bool $smtpNotificationsScheduledTasks = false; #[Validate(['boolean'])] - public bool $smtpNotificationsServerDiskUsage; + public bool $smtpNotificationsServerDiskUsage = false; #[Validate(['boolean'])] public bool $resendEnabled; @@ -73,6 +73,9 @@ class Email extends Component #[Validate(['nullable', 'string'])] public ?string $resendApiKey = null; + #[Validate(['required', 'email'])] + public string $testEmailAddress = ''; + public function mount() { try { @@ -132,14 +135,21 @@ class Email extends Component } } - public function sendTestNotification() + public function sendTestEmail() { try { + $this->validate([ + 'testEmailAddress' => 'required|email', + ], [ + 'testEmailAddress.required' => 'Test email address is required.', + 'testEmailAddress.email' => 'Please enter a valid email address.', + ]); + $executed = RateLimiter::attempt( 'test-email:'.$this->team->id, $perMinute = 0, function () { - $this->team?->notify(new Test($this->emails)); + $this->team?->notify(new Test($this->testEmailAddress)); $this->dispatch('success', 'Test Email sent.'); }, $decaySeconds = 10, @@ -187,6 +197,7 @@ class Email extends Component { try { $this->validate([ + 'resendApiKey' => 'required', ], [ 'resendApiKey.required' => 'Resend API Key is required.', ]); diff --git a/app/Livewire/Project/Application/Configuration.php b/app/Livewire/Project/Application/Configuration.php index d4ec8f581..5e7f83772 100644 --- a/app/Livewire/Project/Application/Configuration.php +++ b/app/Livewire/Project/Application/Configuration.php @@ -16,24 +16,33 @@ class Configuration extends Component public function mount() { - $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); - if (! $project) { - return redirect()->route('dashboard'); - } - $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); - if (! $environment) { - return redirect()->route('dashboard'); - } - $application = $environment->applications->where('uuid', request()->route('application_uuid'))->first(); - if (! $application) { - return redirect()->route('dashboard'); - } + $project = currentTeam() + ->projects() + ->select('id', 'uuid', 'team_id') + ->where('uuid', request()->route('project_uuid')) + ->firstOrFail(); + + $environment = $project->environments() + ->select('id', 'name', 'project_id') + ->where('name', request()->route('environment_name')) + ->firstOrFail(); + + $application = $environment->applications() + ->with(['destination']) + ->where('uuid', request()->route('application_uuid')) + ->firstOrFail(); + $this->application = $application; - $mainServer = $this->application->destination->server; - $servers = Server::ownedByCurrentTeam()->get(); - $this->servers = $servers->filter(function ($server) use ($mainServer) { - return $server->id != $mainServer->id; - }); + + if ($application->destination && $application->destination->server) { + $mainServer = $application->destination->server; + $this->servers = Server::ownedByCurrentTeam() + ->select('id', 'name') + ->where('id', '!=', $mainServer->id) + ->get(); + } else { + $this->servers = collect(); + } } public function render() diff --git a/app/Livewire/Project/Application/General.php b/app/Livewire/Project/Application/General.php index f1575a01f..ff29b74e9 100644 --- a/app/Livewire/Project/Application/General.php +++ b/app/Livewire/Project/Application/General.php @@ -84,6 +84,7 @@ class General extends Component 'application.pre_deployment_command_container' => 'nullable', 'application.post_deployment_command' => 'nullable', 'application.post_deployment_command_container' => 'nullable', + 'application.custom_nginx_configuration' => 'nullable', 'application.settings.is_static' => 'boolean|required', 'application.settings.is_build_server_enabled' => 'boolean|required', 'application.settings.is_container_label_escape_enabled' => 'boolean|required', @@ -121,6 +122,7 @@ class General extends Component 'application.custom_docker_run_options' => 'Custom docker run commands', 'application.docker_compose_custom_start_command' => 'Docker compose custom start command', 'application.docker_compose_custom_build_command' => 'Docker compose custom build command', + 'application.custom_nginx_configuration' => 'Custom Nginx configuration', 'application.settings.is_static' => 'Is static', 'application.settings.is_build_server_enabled' => 'Is build server enabled', 'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled', @@ -241,6 +243,13 @@ class General extends Component } } + public function updatedApplicationSettingsIsStatic($value) + { + if ($value) { + $this->generateNginxConfiguration(); + } + } + public function updatedApplicationBuildPack() { if ($this->application->build_pack !== 'nixpacks') { @@ -257,6 +266,7 @@ class General extends Component if ($this->application->build_pack === 'static') { $this->application->ports_exposes = $this->ports_exposes = 80; $this->resetDefaultLabels(false); + $this->generateNginxConfiguration(); } $this->submit(); $this->dispatch('buildPackUpdated'); @@ -274,6 +284,13 @@ class General extends Component } } + public function generateNginxConfiguration() + { + $this->application->custom_nginx_configuration = defaultNginxConfiguration(); + $this->application->save(); + $this->dispatch('success', 'Nginx configuration generated.'); + } + public function resetDefaultLabels($manualReset = false) { try { diff --git a/app/Livewire/Project/Application/Heading.php b/app/Livewire/Project/Application/Heading.php index 1082b48cd..19a6145b7 100644 --- a/app/Livewire/Project/Application/Heading.php +++ b/app/Livewire/Project/Application/Heading.php @@ -36,7 +36,11 @@ class Heading extends Component public function mount() { - $this->parameters = get_route_parameters(); + $this->parameters = [ + 'project_uuid' => $this->application->project()->uuid, + 'environment_name' => $this->application->environment->name, + 'application_uuid' => $this->application->uuid, + ]; $lastDeployment = $this->application->get_last_successful_deployment(); $this->lastDeploymentInfo = data_get_str($lastDeployment, 'commit')->limit(7).' '.data_get($lastDeployment, 'commit_message'); $this->lastDeploymentLink = $this->application->gitCommitLink(data_get($lastDeployment, 'commit')); @@ -45,13 +49,11 @@ class Heading extends Component public function check_status($showNotification = false) { if ($this->application->destination->server->isFunctional()) { - GetContainersStatus::dispatch($this->application->destination->server)->onQueue('high'); + GetContainersStatus::dispatch($this->application->destination->server); } if ($showNotification) { $this->dispatch('success', 'Success', 'Application status updated.'); } - // Removed because it caused flickering - // $this->dispatch('configurationChanged'); } public function force_deploy_without_cache() diff --git a/app/Livewire/Project/Application/Preview/Form.php b/app/Livewire/Project/Application/Preview/Form.php index c7b2e8184..edcab44c8 100644 --- a/app/Livewire/Project/Application/Preview/Form.php +++ b/app/Livewire/Project/Application/Preview/Form.php @@ -3,6 +3,7 @@ namespace App\Livewire\Project\Application\Preview; use App\Models\Application; +use Livewire\Attributes\Validate; use Livewire\Component; use Spatie\Url\Url; diff --git a/app/Livewire/Project/Database/BackupEdit.php b/app/Livewire/Project/Database/BackupEdit.php index bcf2f959e..b3a54f0ab 100644 --- a/app/Livewire/Project/Database/BackupEdit.php +++ b/app/Livewire/Project/Database/BackupEdit.php @@ -46,8 +46,8 @@ class BackupEdit extends Component #[Validate(['required', 'boolean'])] public bool $saveS3 = false; - #[Validate(['required', 'integer'])] - public int $s3StorageId = 1; + #[Validate(['nullable', 'integer'])] + public ?int $s3StorageId = 1; #[Validate(['nullable', 'string'])] public ?string $databasesToBackup = null; diff --git a/app/Livewire/Project/Database/CreateScheduledBackup.php b/app/Livewire/Project/Database/CreateScheduledBackup.php index 0903efdfd..01108c290 100644 --- a/app/Livewire/Project/Database/CreateScheduledBackup.php +++ b/app/Livewire/Project/Database/CreateScheduledBackup.php @@ -21,8 +21,8 @@ class CreateScheduledBackup extends Component public bool $enabled = true; - #[Validate(['required', 'integer'])] - public int $s3StorageId; + #[Validate(['nullable', 'integer'])] + public ?int $s3StorageId = null; public Collection $definedS3s; @@ -49,6 +49,7 @@ class CreateScheduledBackup extends Component return; } + $payload = [ 'enabled' => true, 'frequency' => $this->frequency, @@ -58,6 +59,7 @@ class CreateScheduledBackup extends Component 'database_type' => $this->database->getMorphClass(), 'team_id' => currentTeam()->id, ]; + if ($this->database->type() === 'standalone-postgresql') { $payload['databases_to_backup'] = $this->database->postgres_db; } elseif ($this->database->type() === 'standalone-mysql') { @@ -72,11 +74,11 @@ class CreateScheduledBackup extends Component } else { $this->dispatch('refreshScheduledBackups'); } + } catch (\Throwable $e) { return handleError($e, $this); } finally { $this->frequency = ''; - $this->saveToS3 = true; } } } diff --git a/app/Livewire/Project/DeleteEnvironment.php b/app/Livewire/Project/DeleteEnvironment.php index 6d8c3aff7..1ee5de269 100644 --- a/app/Livewire/Project/DeleteEnvironment.php +++ b/app/Livewire/Project/DeleteEnvironment.php @@ -37,6 +37,6 @@ class DeleteEnvironment extends Component return redirect()->route('project.show', parameters: ['project_uuid' => $this->parameters['project_uuid']]); } - return $this->dispatch('error', 'Environment has defined resources, please delete them first.'); + return $this->dispatch('error', "Environment {$environment->name} has defined resources, please delete them first."); } } diff --git a/app/Livewire/Project/DeleteProject.php b/app/Livewire/Project/DeleteProject.php index 360fad10a..f320a19b0 100644 --- a/app/Livewire/Project/DeleteProject.php +++ b/app/Livewire/Project/DeleteProject.php @@ -27,11 +27,12 @@ class DeleteProject extends Component 'project_id' => 'required|int', ]); $project = Project::findOrFail($this->project_id); - if ($project->applications->count() > 0) { - return $this->dispatch('error', 'Project has resources defined, please delete them first.'); - } - $project->delete(); + if ($project->isEmpty()) { + $project->delete(); - return redirect()->route('project.index'); + return redirect()->route('project.index'); + } + + return $this->dispatch('error', "Project {$project->name} has resources defined, please delete them first."); } } diff --git a/app/Livewire/Project/New/Select.php b/app/Livewire/Project/New/Select.php index a562d8b66..0b6d075a4 100644 --- a/app/Livewire/Project/New/Select.php +++ b/app/Livewire/Project/New/Select.php @@ -91,9 +91,16 @@ class Select extends Component { $services = get_service_templates(true); $services = collect($services)->map(function ($service, $key) { + $default_logo = 'images/default.webp'; + $logo = data_get($service, 'logo', $default_logo); + $local_logo_path = public_path($logo); + return [ 'name' => str($key)->headline(), - 'logo' => asset(data_get($service, 'logo', 'svgs/coolify.png')), + 'logo' => asset($logo), + 'logo_github_url' => file_exists($local_logo_path) + ? 'https://raw.githubusercontent.com/coollabsio/coolify/refs/heads/main/public/'.$logo + : asset($default_logo), ] + (array) $service; })->all(); $gitBasedApplications = [ @@ -141,14 +148,14 @@ class Select extends Component 'id' => 'postgresql', 'name' => 'PostgreSQL', 'description' => 'PostgreSQL is an object-relational database known for its robustness, advanced features, and strong standards compliance.', - 'logo' => ' + 'logo' => ' ', ], [ 'id' => 'mysql', 'name' => 'MySQL', 'description' => 'MySQL is an open-source relational database management system. ', - 'logo' => ' + 'logo' => ' @@ -158,38 +165,38 @@ class Select extends Component [ 'id' => 'mariadb', 'name' => 'MariaDB', - 'description' => 'MariaDB is a community-developed, commercially supported fork of the MySQL relational database management system, intended to remain free and open-source software under the GNU General Public License.', - 'logo' => '', + 'description' => 'MariaDB is a community-developed, commercially supported fork of the MySQL relational database management system, intended to remain free and open-source.', + 'logo' => '', ], [ 'id' => 'redis', 'name' => 'Redis', 'description' => 'Redis is a source-available, in-memory storage, used as a distributed, in-memory key–value database, cache and message broker, with optional durability.', - 'logo' => '', + 'logo' => '', ], [ 'id' => 'keydb', 'name' => 'KeyDB', 'description' => 'KeyDB is a database that offers high performance, low latency, and scalability for various data structures and workloads.', - 'logo' => '
', + 'logo' => '
', ], [ 'id' => 'dragonfly', 'name' => 'Dragonfly', 'description' => 'Dragonfly DB is a drop-in Redis replacement that delivers 25x more throughput and 12x faster snapshotting than Redis.', - 'logo' => '
', + 'logo' => '
', ], [ 'id' => 'mongodb', 'name' => 'MongoDB', 'description' => 'MongoDB is a source-available, cross-platform, document-oriented database program.', - 'logo' => '', + 'logo' => '', ], [ 'id' => 'clickhouse', 'name' => 'ClickHouse', 'description' => 'ClickHouse is a column-oriented database that supports real-time analytics, business intelligence, observability, ML and GenAI, and more.', - 'logo' => '
', + 'logo' => '
', ], ]; diff --git a/app/Livewire/Project/Shared/ScheduledTask/Executions.php b/app/Livewire/Project/Shared/ScheduledTask/Executions.php index 05d9a7a13..0710e37ff 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Executions.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Executions.php @@ -2,23 +2,60 @@ namespace App\Livewire\Project\Shared\ScheduledTask; +use App\Models\ScheduledTask; +use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Auth; +use Livewire\Attributes\Locked; use Livewire\Component; class Executions extends Component { - public $executions = []; + public ScheduledTask $task; - public $selectedKey; + #[Locked] + public int $taskId; - public $task; + #[Locked] + public Collection $executions; + + #[Locked] + public ?int $selectedKey = null; + + #[Locked] + public ?string $serverTimezone = null; public function getListeners() { + $teamId = Auth::user()->currentTeam()->id; + return [ - 'selectTask', + "echo-private:team.{$teamId},ScheduledTaskDone" => 'refreshExecutions', ]; } + public function mount($taskId) + { + try { + $this->taskId = $taskId; + $this->task = ScheduledTask::findOrFail($taskId); + $this->executions = $this->task->executions()->take(20)->get(); + $this->serverTimezone = data_get($this->task, 'application.destination.server.settings.server_timezone'); + if (! $this->serverTimezone) { + $this->serverTimezone = data_get($this->task, 'service.destination.server.settings.server_timezone'); + } + if (! $this->serverTimezone) { + $this->serverTimezone = 'UTC'; + } + } catch (\Exception $e) { + return handleError($e); + } + } + + public function refreshExecutions(): void + { + $this->executions = $this->task->executions()->take(20)->get(); + } + public function selectTask($key): void { if ($key == $this->selectedKey) { @@ -29,38 +66,9 @@ class Executions extends Component $this->selectedKey = $key; } - public function server() - { - if (! $this->task) { - return null; - } - - if ($this->task->application) { - if ($this->task->application->destination && $this->task->application->destination->server) { - return $this->task->application->destination->server; - } - } elseif ($this->task->service) { - if ($this->task->service->destination && $this->task->service->destination->server) { - return $this->task->service->destination->server; - } - } - - return null; - } - - public function getServerTimezone() - { - $server = $this->server(); - if (! $server) { - return 'UTC'; - } - - return $server->settings->server_timezone; - } - public function formatDateInServerTimezone($date) { - $serverTimezone = $this->getServerTimezone(); + $serverTimezone = $this->serverTimezone; $dateObj = new \DateTime($date); try { $dateObj->setTimezone(new \DateTimeZone($serverTimezone)); diff --git a/app/Livewire/Project/Shared/ScheduledTask/Show.php b/app/Livewire/Project/Shared/ScheduledTask/Show.php index 36194edb7..0900a1d70 100644 --- a/app/Livewire/Project/Shared/ScheduledTask/Show.php +++ b/app/Livewire/Project/Shared/ScheduledTask/Show.php @@ -2,74 +2,124 @@ namespace App\Livewire\Project\Shared\ScheduledTask; +use App\Jobs\ScheduledTaskJob; use App\Models\Application; -use App\Models\ScheduledTask as ModelsScheduledTask; +use App\Models\ScheduledTask; use App\Models\Service; +use Livewire\Attributes\Locked; +use Livewire\Attributes\Validate; use Livewire\Component; -use Visus\Cuid2\Cuid2; class Show extends Component { - public $parameters; - public Application|Service $resource; - public ModelsScheduledTask $task; + public ScheduledTask $task; - public ?string $modalId = null; + #[Locked] + public array $parameters; + #[Locked] public string $type; - public string $scheduledTaskName; + #[Validate(['boolean'])] + public bool $isEnabled = false; - protected $rules = [ - 'task.enabled' => 'required|boolean', - 'task.name' => 'required|string', - 'task.command' => 'required|string', - 'task.frequency' => 'required|string', - 'task.container' => 'nullable|string', - ]; + #[Validate(['string', 'required'])] + public string $name; - protected $validationAttributes = [ - 'name' => 'name', - 'command' => 'command', - 'frequency' => 'frequency', - 'container' => 'container', - ]; + #[Validate(['string', 'required'])] + public string $command; - public function mount() + #[Validate(['string', 'required'])] + public string $frequency; + + #[Validate(['string', 'nullable'])] + public ?string $container = null; + + #[Locked] + public ?string $application_uuid; + + #[Locked] + public ?string $service_uuid; + + #[Locked] + public string $task_uuid; + + public function mount(string $task_uuid, string $project_uuid, string $environment_name, ?string $application_uuid = null, ?string $service_uuid = null) { - $this->parameters = get_route_parameters(); + try { + $this->task_uuid = $task_uuid; + if ($application_uuid) { + $this->type = 'application'; + $this->application_uuid = $application_uuid; + $this->resource = Application::ownedByCurrentTeam()->where('uuid', $application_uuid)->firstOrFail(); + } elseif ($service_uuid) { + $this->type = 'service'; + $this->service_uuid = $service_uuid; + $this->resource = Service::ownedByCurrentTeam()->where('uuid', $service_uuid)->firstOrFail(); + } + $this->parameters = [ + 'environment_name' => $environment_name, + 'project_uuid' => $project_uuid, + 'application_uuid' => $application_uuid, + 'service_uuid' => $service_uuid, + ]; - if (data_get($this->parameters, 'application_uuid')) { - $this->type = 'application'; - $this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail(); - } elseif (data_get($this->parameters, 'service_uuid')) { - $this->type = 'service'; - $this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail(); + $this->task = $this->resource->scheduled_tasks()->where('uuid', $task_uuid)->firstOrFail(); + $this->syncData(); + } catch (\Exception $e) { + return handleError($e); } + } - $this->modalId = new Cuid2; - $this->task = ModelsScheduledTask::where('uuid', request()->route('task_uuid'))->first(); - $this->scheduledTaskName = $this->task->name; + public function syncData(bool $toModel = false) + { + if ($toModel) { + $this->validate(); + $this->task->enabled = $this->isEnabled; + $this->task->name = str($this->name)->trim()->value(); + $this->task->command = str($this->command)->trim()->value(); + $this->task->frequency = str($this->frequency)->trim()->value(); + $this->task->container = str($this->container)->trim()->value(); + $this->task->save(); + } else { + $this->isEnabled = $this->task->enabled; + $this->name = $this->task->name; + $this->command = $this->task->command; + $this->frequency = $this->task->frequency; + $this->container = $this->task->container; + } } public function instantSave() { - $this->validateOnly('task.enabled'); - $this->task->save(['enabled' => $this->task->enabled]); - $this->dispatch('success', 'Scheduled task updated.'); - $this->dispatch('refreshTasks'); + try { + $this->syncData(true); + $this->dispatch('success', 'Scheduled task updated.'); + $this->refreshTasks(); + } catch (\Exception $e) { + return handleError($e); + } } public function submit() { - $this->validate(); - $this->task->name = str($this->task->name)->trim()->value(); - $this->task->container = str($this->task->container)->trim()->value(); - $this->task->save(); - $this->dispatch('success', 'Scheduled task updated.'); - $this->dispatch('refreshTasks'); + try { + $this->syncData(true); + $this->dispatch('success', 'Scheduled task updated.'); + } catch (\Exception $e) { + return handleError($e); + } + } + + public function refreshTasks() + { + try { + $this->task->refresh(); + } catch (\Exception $e) { + return handleError($e); + } } public function delete() @@ -78,12 +128,22 @@ class Show extends Component $this->task->delete(); if ($this->type === 'application') { - return redirect()->route('project.application.configuration', $this->parameters, $this->scheduledTaskName); + return redirect()->route('project.application.configuration', $this->parameters, $this->task->name); } else { - return redirect()->route('project.service.configuration', $this->parameters, $this->scheduledTaskName); + return redirect()->route('project.service.configuration', $this->parameters, $this->task->name); } } catch (\Exception $e) { return handleError($e); } } + + public function executeNow() + { + try { + ScheduledTaskJob::dispatch($this->task); + $this->dispatch('success', 'Scheduled task executed.'); + } catch (\Exception $e) { + return handleError($e); + } + } } diff --git a/app/Livewire/Project/Shared/Tags.php b/app/Livewire/Project/Shared/Tags.php index dca6180ff..811859cb8 100644 --- a/app/Livewire/Project/Shared/Tags.php +++ b/app/Livewire/Project/Shared/Tags.php @@ -37,6 +37,7 @@ class Tags extends Component $this->validate(); $tags = str($this->newTags)->trim()->explode(' '); foreach ($tags as $tag) { + $tag = strip_tags($tag); if (strlen($tag) < 2) { $this->dispatch('error', 'Invalid tag.', "Tag $tag is invalid. Min length is 2."); @@ -65,6 +66,7 @@ class Tags extends Component public function addTag(string $id, string $name) { try { + $name = strip_tags($name); if ($this->resource->tags()->where('id', $id)->exists()) { $this->dispatch('error', 'Duplicate tags.', "Tag $name already added."); diff --git a/app/Livewire/Server/Advanced.php b/app/Livewire/Server/Advanced.php index 0852abebf..0650de9a0 100644 --- a/app/Livewire/Server/Advanced.php +++ b/app/Livewire/Server/Advanced.php @@ -22,7 +22,7 @@ class Advanced extends Component #[Validate('boolean')] public bool $forceDockerCleanup = false; - #[Validate('string')] + #[Validate(['string', 'required'])] public string $dockerCleanupFrequency = '*/10 * * * *'; #[Validate(['integer', 'min:1', 'max:99'])] @@ -78,7 +78,6 @@ class Advanced extends Component try { $this->syncData(true); $this->dispatch('success', 'Server updated.'); - // $this->dispatch('refreshServerShow'); } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Server/CloudflareTunnels.php b/app/Livewire/Server/CloudflareTunnels.php index 311196473..f69fc8655 100644 --- a/app/Livewire/Server/CloudflareTunnels.php +++ b/app/Livewire/Server/CloudflareTunnels.php @@ -17,6 +17,9 @@ class CloudflareTunnels extends Component { try { $this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail(); + if ($this->server->isLocalhost()) { + return redirect()->route('server.show', ['server_uuid' => $server_uuid]); + } $this->isCloudflareTunnelsEnabled = $this->server->settings->is_cloudflare_tunnel; } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Livewire/Server/LogDrains.php b/app/Livewire/Server/LogDrains.php index 6599149c4..edddfc755 100644 --- a/app/Livewire/Server/LogDrains.php +++ b/app/Livewire/Server/LogDrains.php @@ -49,33 +49,73 @@ class LogDrains extends Component } } - public function syncData(bool $toModel = false) + public function syncDataNewRelic(bool $toModel = false) + { + if ($toModel) { + $this->server->settings->is_logdrain_newrelic_enabled = $this->isLogDrainNewRelicEnabled; + $this->server->settings->logdrain_newrelic_license_key = $this->logDrainNewRelicLicenseKey; + $this->server->settings->logdrain_newrelic_base_uri = $this->logDrainNewRelicBaseUri; + } else { + $this->isLogDrainNewRelicEnabled = $this->server->settings->is_logdrain_newrelic_enabled; + $this->logDrainNewRelicLicenseKey = $this->server->settings->logdrain_newrelic_license_key; + $this->logDrainNewRelicBaseUri = $this->server->settings->logdrain_newrelic_base_uri; + } + } + + public function syncDataAxiom(bool $toModel = false) + { + if ($toModel) { + $this->server->settings->is_logdrain_axiom_enabled = $this->isLogDrainAxiomEnabled; + $this->server->settings->logdrain_axiom_dataset_name = $this->logDrainAxiomDatasetName; + $this->server->settings->logdrain_axiom_api_key = $this->logDrainAxiomApiKey; + } else { + $this->isLogDrainAxiomEnabled = $this->server->settings->is_logdrain_axiom_enabled; + $this->logDrainAxiomDatasetName = $this->server->settings->logdrain_axiom_dataset_name; + $this->logDrainAxiomApiKey = $this->server->settings->logdrain_axiom_api_key; + } + } + + public function syncDataCustom(bool $toModel = false) + { + if ($toModel) { + $this->server->settings->is_logdrain_custom_enabled = $this->isLogDrainCustomEnabled; + $this->server->settings->logdrain_custom_config = $this->logDrainCustomConfig; + $this->server->settings->logdrain_custom_config_parser = $this->logDrainCustomConfigParser; + } else { + $this->isLogDrainCustomEnabled = $this->server->settings->is_logdrain_custom_enabled; + $this->logDrainCustomConfig = $this->server->settings->logdrain_custom_config; + $this->logDrainCustomConfigParser = $this->server->settings->logdrain_custom_config_parser; + } + } + + public function syncData(bool $toModel = false, ?string $type = null) { if ($toModel) { $this->customValidation(); - $this->server->settings->is_logdrain_newrelic_enabled = $this->isLogDrainNewRelicEnabled; - $this->server->settings->is_logdrain_axiom_enabled = $this->isLogDrainAxiomEnabled; - $this->server->settings->is_logdrain_custom_enabled = $this->isLogDrainCustomEnabled; - - $this->server->settings->logdrain_newrelic_license_key = $this->logDrainNewRelicLicenseKey; - $this->server->settings->logdrain_newrelic_base_uri = $this->logDrainNewRelicBaseUri; - $this->server->settings->logdrain_axiom_dataset_name = $this->logDrainAxiomDatasetName; - $this->server->settings->logdrain_axiom_api_key = $this->logDrainAxiomApiKey; - $this->server->settings->logdrain_custom_config = $this->logDrainCustomConfig; - $this->server->settings->logdrain_custom_config_parser = $this->logDrainCustomConfigParser; - + if ($type === 'newrelic') { + $this->syncDataNewRelic($toModel); + } elseif ($type === 'axiom') { + $this->syncDataAxiom($toModel); + } elseif ($type === 'custom') { + $this->syncDataCustom($toModel); + } else { + $this->syncDataNewRelic($toModel); + $this->syncDataAxiom($toModel); + $this->syncDataCustom($toModel); + } $this->server->settings->save(); } else { - $this->isLogDrainNewRelicEnabled = $this->server->settings->is_logdrain_newrelic_enabled; - $this->isLogDrainAxiomEnabled = $this->server->settings->is_logdrain_axiom_enabled; - $this->isLogDrainCustomEnabled = $this->server->settings->is_logdrain_custom_enabled; - - $this->logDrainNewRelicLicenseKey = $this->server->settings->logdrain_newrelic_license_key; - $this->logDrainNewRelicBaseUri = $this->server->settings->logdrain_newrelic_base_uri; - $this->logDrainAxiomDatasetName = $this->server->settings->logdrain_axiom_dataset_name; - $this->logDrainAxiomApiKey = $this->server->settings->logdrain_axiom_api_key; - $this->logDrainCustomConfig = $this->server->settings->logdrain_custom_config; - $this->logDrainCustomConfigParser = $this->server->settings->logdrain_custom_config_parser; + if ($type === 'newrelic') { + $this->syncDataNewRelic($toModel); + } elseif ($type === 'axiom') { + $this->syncDataAxiom($toModel); + } elseif ($type === 'custom') { + $this->syncDataCustom($toModel); + } else { + $this->syncDataNewRelic($toModel); + $this->syncDataAxiom($toModel); + $this->syncDataCustom($toModel); + } } } @@ -136,7 +176,7 @@ class LogDrains extends Component public function submit(string $type) { try { - $this->syncData(true); + $this->syncData(true, $type); $this->dispatch('success', 'Settings saved.'); } catch (\Throwable $e) { return handleError($e, $this); diff --git a/app/Livewire/Server/New/ByIp.php b/app/Livewire/Server/New/ByIp.php index f80152435..5f60c5db5 100644 --- a/app/Livewire/Server/New/ByIp.php +++ b/app/Livewire/Server/New/ByIp.php @@ -6,64 +6,60 @@ use App\Enums\ProxyTypes; use App\Models\Server; use App\Models\Team; use Illuminate\Support\Collection; +use Livewire\Attributes\Locked; +use Livewire\Attributes\Validate; use Livewire\Component; class ByIp extends Component { + #[Locked] public $private_keys; + #[Locked] public $limit_reached; + #[Validate('nullable|integer', as: 'Private Key')] public ?int $private_key_id = null; + #[Validate('nullable|string', as: 'Private Key Name')] public $new_private_key_name; + #[Validate('nullable|string', as: 'Private Key Description')] public $new_private_key_description; + #[Validate('nullable|string', as: 'Private Key Value')] public $new_private_key_value; + #[Validate('required|string', as: 'Name')] public string $name; + #[Validate('nullable|string', as: 'Description')] public ?string $description = null; + #[Validate('required|string', as: 'IP Address/Domain')] public string $ip; + #[Validate('required|string', as: 'User')] public string $user = 'root'; + #[Validate('required|integer|between:1,65535', as: 'Port')] public int $port = 22; + #[Validate('required|boolean', as: 'Swarm Manager')] public bool $is_swarm_manager = false; + #[Validate('required|boolean', as: 'Swarm Worker')] public bool $is_swarm_worker = false; + #[Validate('nullable|integer', as: 'Swarm Cluster')] public $selected_swarm_cluster = null; + #[Validate('required|boolean', as: 'Build Server')] public bool $is_build_server = false; + #[Locked] public Collection $swarm_managers; - protected $rules = [ - 'name' => 'required|string', - 'description' => 'nullable|string', - 'ip' => 'required', - 'user' => 'required|string', - 'port' => 'required|integer', - 'is_swarm_manager' => 'required|boolean', - 'is_swarm_worker' => 'required|boolean', - 'is_build_server' => 'required|boolean', - ]; - - protected $validationAttributes = [ - 'name' => 'Name', - 'description' => 'Description', - 'ip' => 'IP Address/Domain', - 'user' => 'User', - 'port' => 'Port', - 'is_swarm_manager' => 'Swarm Manager', - 'is_swarm_worker' => 'Swarm Worker', - 'is_build_server' => 'Build Server', - ]; - public function mount() { $this->name = generate_random_name(); @@ -88,6 +84,12 @@ class ByIp extends Component { $this->validate(); try { + if (Server::where('team_id', currentTeam()->id) + ->where('ip', $this->ip) + ->exists()) { + return $this->dispatch('error', 'This IP/Domain is already in use by another server in your team.'); + } + if (is_null($this->private_key_id)) { return $this->dispatch('error', 'You must select a private key'); } diff --git a/app/Livewire/Server/Proxy.php b/app/Livewire/Server/Proxy.php index 94ea3509a..0b069ddb9 100644 --- a/app/Livewire/Server/Proxy.php +++ b/app/Livewire/Server/Proxy.php @@ -4,7 +4,6 @@ namespace App\Livewire\Server; use App\Actions\Proxy\CheckConfiguration; use App\Actions\Proxy\SaveConfiguration; -use App\Actions\Proxy\StartProxy; use App\Models\Server; use Livewire\Component; @@ -44,14 +43,13 @@ class Proxy extends Component public function selectProxy($proxy_type) { - $this->server->proxy->set('status', 'exited'); - $this->server->proxy->set('type', $proxy_type); - $this->server->save(); - $this->selectedProxy = $this->server->proxy->type; - if ($this->server->proxySet()) { - StartProxy::run($this->server, false); + try { + $this->server->changeProxy($proxy_type, async: false); + $this->selectedProxy = $this->server->proxy->type; + $this->dispatch('proxyStatusUpdated'); + } catch (\Throwable $e) { + return handleError($e, $this); } - $this->dispatch('proxyStatusUpdated'); } public function instantSave() diff --git a/app/Livewire/Server/Show.php b/app/Livewire/Server/Show.php index 524e21908..a5544489d 100644 --- a/app/Livewire/Server/Show.php +++ b/app/Livewire/Server/Show.php @@ -5,6 +5,7 @@ namespace App\Livewire\Server; use App\Actions\Server\StartSentinel; use App\Actions\Server\StopSentinel; use App\Models\Server; +use Livewire\Attributes\Locked; use Livewire\Attributes\Validate; use Livewire\Component; @@ -16,7 +17,7 @@ class Show extends Component public string $name; #[Validate(['nullable'])] - public ?string $description; + public ?string $description = null; #[Validate(['required'])] public string $ip; @@ -31,7 +32,7 @@ class Show extends Component public ?string $validationLogs = null; #[Validate(['nullable', 'url'])] - public ?string $wildcardDomain; + public ?string $wildcardDomain = null; #[Validate(['required'])] public bool $isReachable; @@ -55,7 +56,7 @@ class Show extends Component public string $sentinelToken; #[Validate(['nullable'])] - public ?string $sentinelUpdatedAt; + public ?string $sentinelUpdatedAt = null; #[Validate(['required', 'integer', 'min:1'])] public int $sentinelMetricsRefreshRateSeconds; @@ -67,7 +68,7 @@ class Show extends Component public int $sentinelPushIntervalSeconds; #[Validate(['nullable', 'url'])] - public ?string $sentinelCustomUrl; + public ?string $sentinelCustomUrl = null; #[Validate(['required'])] public bool $isSentinelEnabled; @@ -78,6 +79,7 @@ class Show extends Component #[Validate(['required'])] public string $serverTimezone; + #[Locked] public array $timezones; public function getListeners() @@ -105,6 +107,15 @@ class Show extends Component { if ($toModel) { $this->validate(); + + if (Server::where('team_id', currentTeam()->id) + ->where('ip', $this->ip) + ->where('id', '!=', $this->server->id) + ->exists()) { + $this->ip = $this->server->ip; + throw new \Exception('This IP/Domain is already in use by another server in your team.'); + } + $this->server->name = $this->name; $this->server->description = $this->description; $this->server->ip = $this->ip; @@ -114,6 +125,7 @@ class Show extends Component $this->server->save(); $this->server->settings->is_swarm_manager = $this->isSwarmManager; + $this->server->settings->wildcard_domain = $this->wildcardDomain; $this->server->settings->is_swarm_worker = $this->isSwarmWorker; $this->server->settings->is_build_server = $this->isBuildServer; $this->server->settings->is_metrics_enabled = $this->isMetricsEnabled; @@ -124,7 +136,14 @@ class Show extends Component $this->server->settings->sentinel_custom_url = $this->sentinelCustomUrl; $this->server->settings->is_sentinel_enabled = $this->isSentinelEnabled; $this->server->settings->is_sentinel_debug_enabled = $this->isSentinelDebugEnabled; - $this->server->settings->server_timezone = $this->serverTimezone; + + if (! validate_timezone($this->serverTimezone)) { + $this->serverTimezone = config('app.timezone'); + throw new \Exception('Invalid timezone.'); + } else { + $this->server->settings->server_timezone = $this->serverTimezone; + } + $this->server->settings->save(); } else { $this->name = $this->server->name; @@ -132,6 +151,7 @@ class Show extends Component $this->ip = $this->server->ip; $this->user = $this->server->user; $this->port = $this->server->port; + $this->wildcardDomain = $this->server->settings->wildcard_domain; $this->isReachable = $this->server->settings->is_reachable; $this->isUsable = $this->server->settings->is_usable; diff --git a/app/Livewire/Server/ValidateAndInstall.php b/app/Livewire/Server/ValidateAndInstall.php index 8c5bc23ed..791ef9350 100644 --- a/app/Livewire/Server/ValidateAndInstall.php +++ b/app/Livewire/Server/ValidateAndInstall.php @@ -159,7 +159,8 @@ class ValidateAndInstall extends Component $this->dispatch('refreshBoardingIndex'); $this->dispatch('success', 'Server validated.'); } else { - $this->error = 'Docker Engine version is not 22+. Please install Docker manually before continuing: documentation.'; + $requiredDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.'); + $this->error = 'Minimum Docker Engine version '.$requiredDockerVersion.' is not instaled. Please install Docker manually before continuing: documentation.'; $this->server->update([ 'validation_logs' => $this->error, ]); diff --git a/app/Livewire/Settings/Index.php b/app/Livewire/Settings/Index.php index 2991b8ae8..31dd13c52 100644 --- a/app/Livewire/Settings/Index.php +++ b/app/Livewire/Settings/Index.php @@ -53,7 +53,7 @@ class Index extends Component #[Validate('string')] public string $auto_update_frequency; - #[Validate('string')] + #[Validate('string|required')] public string $update_check_frequency; #[Validate('required|string|timezone')] @@ -109,6 +109,13 @@ class Index extends Component public function instantSave($isSave = true) { + $this->validate(); + if ($this->settings->is_auto_update_enabled === true) { + $this->validate([ + 'auto_update_frequency' => ['required', 'string'], + ]); + } + $this->settings->fqdn = $this->fqdn; $this->settings->resale_license = $this->resale_license; $this->settings->public_port_min = $this->public_port_min; @@ -139,6 +146,14 @@ class Index extends Component $error_show = false; $this->server = Server::findOrFail(0); $this->resetErrorBag(); + + if (! validate_timezone($this->instance_timezone)) { + $this->instance_timezone = config('app.timezone'); + throw new \Exception('Invalid timezone.'); + } else { + $this->settings->instance_timezone = $this->instance_timezone; + } + if ($this->settings->public_port_min > $this->settings->public_port_max) { $this->addError('settings.public_port_min', 'The minimum port must be lower than the maximum port.'); diff --git a/app/Livewire/Settings/License.php b/app/Livewire/Settings/License.php deleted file mode 100644 index 79f8269f3..000000000 --- a/app/Livewire/Settings/License.php +++ /dev/null @@ -1,58 +0,0 @@ - 'nullable', - 'settings.is_resale_license_active' => 'nullable', - ]; - - protected $validationAttributes = [ - 'settings.resale_license' => 'License', - 'instance_id' => 'Instance Id (Do not change this)', - 'settings.is_resale_license_active' => 'Is License Active', - ]; - - public function mount() - { - if (! isCloud()) { - abort(404); - } - if (! isInstanceAdmin()) { - return redirect()->route('home'); - } - $this->instance_id = config('app.id'); - $this->settings = instanceSettings(); - } - - public function render() - { - return view('livewire.settings.license'); - } - - public function submit() - { - $this->validate(); - $this->settings->save(); - if ($this->settings->resale_license) { - try { - CheckResaleLicense::run(); - $this->dispatch('reloadWindow'); - } catch (\Throwable $e) { - session()->flash('error', 'Something went wrong. Please contact support.
Error: '.$e->getMessage()); - - return redirect()->route('settings.license'); - } - } - } -} diff --git a/app/Livewire/SettingsBackup.php b/app/Livewire/SettingsBackup.php index 6dc5d6ab3..1b0599ffe 100644 --- a/app/Livewire/SettingsBackup.php +++ b/app/Livewire/SettingsBackup.php @@ -99,6 +99,14 @@ class SettingsBackup extends Component $this->database->refresh(); $this->backup->refresh(); $this->s3s = S3Storage::whereTeamId(0)->get(); + + $this->uuid = $this->database->uuid; + $this->name = $this->database->name; + $this->description = $this->database->description; + $this->postgres_user = $this->database->postgres_user; + $this->postgres_password = $this->database->postgres_password; + $this->executions = $this->backup->executions; + } catch (\Exception $e) { return handleError($e, $this); } diff --git a/app/Livewire/SettingsEmail.php b/app/Livewire/SettingsEmail.php index 0ab5754f2..61f720b3a 100644 --- a/app/Livewire/SettingsEmail.php +++ b/app/Livewire/SettingsEmail.php @@ -63,6 +63,8 @@ class SettingsEmail extends Component $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->resend_enabled = $this->resendEnabled; $this->settings->resend_api_key = $this->resendApiKey; diff --git a/app/Livewire/Source/Github/Create.php b/app/Livewire/Source/Github/Create.php index 103c5c9fb..136d3525e 100644 --- a/app/Livewire/Source/Github/Create.php +++ b/app/Livewire/Source/Github/Create.php @@ -23,7 +23,7 @@ class Create extends Component public function mount() { - $this->name = substr(generate_random_name(), 0, 34); // GitHub Apps names can only be 34 characters long + $this->name = substr(generate_random_name(), 0, 30); } public function createGitHubApp() diff --git a/app/Livewire/Terminal/Index.php b/app/Livewire/Terminal/Index.php index 945b25714..a24a237c5 100644 --- a/app/Livewire/Terminal/Index.php +++ b/app/Livewire/Terminal/Index.php @@ -14,13 +14,25 @@ class Index extends Component public $containers = []; + public bool $isLoadingContainers = true; + public function mount() { if (! auth()->user()->isAdmin()) { abort(403); } $this->servers = Server::isReachable()->get(); - $this->containers = $this->getAllActiveContainers(); + } + + public function loadContainers() + { + try { + $this->containers = $this->getAllActiveContainers(); + } catch (\Exception $e) { + return handleError($e, $this); + } finally { + $this->isLoadingContainers = false; + } } private function getAllActiveContainers() diff --git a/app/Livewire/Upgrade.php b/app/Livewire/Upgrade.php index 88ed88cb7..e50085c64 100644 --- a/app/Livewire/Upgrade.php +++ b/app/Livewire/Upgrade.php @@ -23,6 +23,9 @@ class Upgrade extends Component try { $this->latestVersion = get_latest_version_of_coolify(); $this->isUpgradeAvailable = data_get(InstanceSettings::get(), 'new_version_available', false); + if (isDev()) { + $this->isUpgradeAvailable = true; + } } catch (\Throwable $e) { return handleError($e, $this); } diff --git a/app/Livewire/Waitlist/Index.php b/app/Livewire/Waitlist/Index.php index 422415449..0524b495c 100644 --- a/app/Livewire/Waitlist/Index.php +++ b/app/Livewire/Waitlist/Index.php @@ -27,7 +27,7 @@ class Index extends Component public function mount() { - if (config('coolify.waitlist') == false) { + if (config('constants.waitlist.enabled') == false) { return redirect()->route('register'); } $this->waitingInLine = Waitlist::whereVerified(true)->count(); diff --git a/app/Models/Application.php b/app/Models/Application.php index 91abf2e3a..c284528f1 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -98,6 +98,7 @@ use Visus\Cuid2\Cuid2; 'updated_at' => ['type' => 'string', 'format' => 'date-time', 'description' => 'The date and time when the application was last updated.'], 'deleted_at' => ['type' => 'string', 'format' => 'date-time', 'nullable' => true, 'description' => 'The date and time when the application was deleted.'], 'compose_parsing_version' => ['type' => 'string', 'description' => 'How Coolify parse the compose file.'], + 'custom_nginx_configuration' => ['type' => 'string', 'nullable' => true, 'description' => 'Custom Nginx configuration base64 encoded.'], ] )] @@ -114,11 +115,11 @@ class Application extends BaseModel protected static function booted() { static::saving(function ($application) { - if ($application->fqdn === '') { - $application->fqdn = null; - } $payload = []; if ($application->isDirty('fqdn')) { + if ($application->fqdn === '') { + $application->fqdn = null; + } $payload['fqdn'] = $application->fqdn; } if ($application->isDirty('install_command')) { @@ -139,6 +140,11 @@ class Application extends BaseModel if ($application->isDirty('status')) { $payload['last_online_at'] = now(); } + if ($application->isDirty('custom_nginx_configuration')) { + if ($application->custom_nginx_configuration === '') { + $payload['custom_nginx_configuration'] = null; + } + } if (count($payload) > 0) { $application->forceFill($payload); } @@ -172,6 +178,11 @@ class Application extends BaseModel return Application::whereRelation('environment.project.team', 'id', $teamId)->orderBy('name'); } + public static function ownedByCurrentTeam() + { + return Application::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name'); + } + public function getContainersToStop(bool $previewDeployments = false): array { $containers = $previewDeployments @@ -627,6 +638,14 @@ class Application extends BaseModel ); } + public function customNginxConfiguration(): Attribute + { + return Attribute::make( + set: fn ($value) => base64_encode($value), + get: fn ($value) => base64_decode($value), + ); + } + public function portsExposesArray(): Attribute { return Attribute::make( @@ -857,7 +876,7 @@ class Application extends BaseModel public function isConfigurationChanged(bool $save = false) { - $newConfigHash = $this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect; + $newConfigHash = base64_encode($this->fqdn.$this->git_repository.$this->git_branch.$this->git_commit_sha.$this->build_pack.$this->static_image.$this->install_command.$this->build_command.$this->start_command.$this->ports_exposes.$this->ports_mappings.$this->base_directory.$this->publish_directory.$this->dockerfile.$this->dockerfile_location.$this->custom_labels.$this->custom_docker_run_options.$this->dockerfile_target_build.$this->redirect.$this->custom_nginx_configuration); if ($this->pull_request_id === 0 || $this->pull_request_id === null) { $newConfigHash .= json_encode($this->environment_variables()->get('value')->sort()); } else { @@ -887,21 +906,7 @@ class Application extends BaseModel public function customRepository() { - preg_match('/(?<=:)\d+(?=\/)/', $this->git_repository, $matches); - $port = 22; - if (count($matches) === 1) { - $port = $matches[0]; - $gitHost = str($this->git_repository)->before(':'); - $gitRepo = str($this->git_repository)->after('/'); - $repository = "$gitHost:$gitRepo"; - } else { - $repository = $this->git_repository; - } - - return [ - 'repository' => $repository, - 'port' => $port, - ]; + return convertGitUrl($this->git_repository, $this->deploymentType(), $this->source); } public function generateBaseDir(string $uuid) @@ -934,6 +939,122 @@ class Application extends BaseModel return $git_clone_command; } + public function getGitRemoteStatus(string $deployment_uuid) + { + try { + ['commands' => $lsRemoteCommand] = $this->generateGitLsRemoteCommands(deployment_uuid: $deployment_uuid, exec_in_docker: false); + instant_remote_process([$lsRemoteCommand], $this->destination->server, true); + + return [ + 'is_accessible' => true, + 'error' => null, + ]; + } catch (\RuntimeException $ex) { + return [ + 'is_accessible' => false, + 'error' => $ex->getMessage(), + ]; + } + } + + public function generateGitLsRemoteCommands(string $deployment_uuid, bool $exec_in_docker = true) + { + $branch = $this->git_branch; + ['repository' => $customRepository, 'port' => $customPort] = $this->customRepository(); + $commands = collect([]); + $base_command = 'git ls-remote'; + + if ($this->deploymentType() === 'source') { + $source_html_url = data_get($this, 'source.html_url'); + $url = parse_url(filter_var($source_html_url, FILTER_SANITIZE_URL)); + $source_html_url_host = $url['host']; + $source_html_url_scheme = $url['scheme']; + + if ($this->source->getMorphClass() == 'App\Models\GithubApp') { + if ($this->source->is_public) { + $fullRepoUrl = "{$this->source->html_url}/{$customRepository}"; + $base_command = "{$base_command} {$this->source->html_url}/{$customRepository}"; + } else { + $github_access_token = generate_github_installation_token($this->source); + + if ($exec_in_docker) { + $base_command = "{$base_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git"; + $fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}.git"; + } else { + $base_command = "{$base_command} $source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}"; + $fullRepoUrl = "$source_html_url_scheme://x-access-token:$github_access_token@$source_html_url_host/{$customRepository}"; + } + } + + if ($exec_in_docker) { + $commands->push(executeInDocker($deployment_uuid, $base_command)); + } else { + $commands->push($base_command); + } + + return [ + 'commands' => $commands->implode(' && '), + 'branch' => $branch, + 'fullRepoUrl' => $fullRepoUrl, + ]; + } + } + + if ($this->deploymentType() === 'deploy_key') { + $fullRepoUrl = $customRepository; + $private_key = data_get($this, 'private_key.private_key'); + if (is_null($private_key)) { + throw new RuntimeException('Private key not found. Please add a private key to the application and try again.'); + } + $private_key = base64_encode($private_key); + $base_comamnd = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$customPort} -o Port={$customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$base_command} {$customRepository}"; + + if ($exec_in_docker) { + $commands = collect([ + executeInDocker($deployment_uuid, 'mkdir -p /root/.ssh'), + executeInDocker($deployment_uuid, "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null"), + executeInDocker($deployment_uuid, 'chmod 600 /root/.ssh/id_rsa'), + ]); + } else { + $commands = collect([ + 'mkdir -p /root/.ssh', + "echo '{$private_key}' | base64 -d | tee /root/.ssh/id_rsa > /dev/null", + 'chmod 600 /root/.ssh/id_rsa', + ]); + } + + if ($exec_in_docker) { + $commands->push(executeInDocker($deployment_uuid, $base_comamnd)); + } else { + $commands->push($base_comamnd); + } + + return [ + 'commands' => $commands->implode(' && '), + 'branch' => $branch, + 'fullRepoUrl' => $fullRepoUrl, + ]; + } + + if ($this->deploymentType() === 'other') { + $fullRepoUrl = $customRepository; + $base_command = "{$base_command} {$customRepository}"; + $base_command = $this->setGitImportSettings($deployment_uuid, $base_command, public: true); + + if ($exec_in_docker) { + $commands->push(executeInDocker($deployment_uuid, $base_command)); + } else { + $commands->push($base_command); + } + + return [ + 'commands' => $commands->implode(' && '), + 'branch' => $branch, + 'fullRepoUrl' => $fullRepoUrl, + ]; + } + } + public function generateGitImportCommands(string $deployment_uuid, int $pull_request_id = 0, ?string $git_type = null, bool $exec_in_docker = true, bool $only_checkout = false, ?string $custom_base_dir = null, ?string $commit = null) { $branch = $this->git_branch; @@ -1195,6 +1316,11 @@ class Application extends BaseModel $workdir = rtrim($this->base_directory, '/'); $composeFile = $this->docker_compose_location; $fileList = collect([".$workdir$composeFile"]); + $gitRemoteStatus = $this->getGitRemoteStatus(deployment_uuid: $uuid); + if (! $gitRemoteStatus['is_accessible']) { + throw new \RuntimeException("Failed to read Git source:\n\n{$gitRemoteStatus['error']}"); + } + $commands = collect([ "rm -rf /tmp/{$uuid}", "mkdir -p /tmp/{$uuid}", diff --git a/app/Models/EnvironmentVariable.php b/app/Models/EnvironmentVariable.php index 08f23d7ab..96c57e63e 100644 --- a/app/Models/EnvironmentVariable.php +++ b/app/Models/EnvironmentVariable.php @@ -71,7 +71,7 @@ class EnvironmentVariable extends Model } } $environment_variable->update([ - 'version' => config('version'), + 'version' => config('constants.coolify.version'), ]); }); static::saving(function (EnvironmentVariable $environmentVariable) { diff --git a/app/Models/PrivateKey.php b/app/Models/PrivateKey.php index 065746ede..80015d87f 100644 --- a/app/Models/PrivateKey.php +++ b/app/Models/PrivateKey.php @@ -218,10 +218,12 @@ class PrivateKey extends BaseModel private static function fingerprintExists($fingerprint, $excludeId = null) { - $query = self::where('fingerprint', $fingerprint); + $query = self::query() + ->where('fingerprint', $fingerprint) + ->where('id', '!=', $excludeId); - if (! is_null($excludeId)) { - $query->where('id', '!=', $excludeId); + if (currentTeam()) { + $query->where('team_id', currentTeam()->id); } return $query->exists(); diff --git a/app/Models/Project.php b/app/Models/Project.php index 3a09b0b8f..f27e6c208 100644 --- a/app/Models/Project.php +++ b/app/Models/Project.php @@ -122,9 +122,18 @@ class Project extends BaseModel return $this->hasManyThrough(StandaloneMariadb::class, Environment::class); } - public function resource_count() + public function isEmpty() { - return $this->applications()->count() + $this->postgresqls()->count() + $this->redis()->count() + $this->mongodbs()->count() + $this->mysqls()->count() + $this->mariadbs()->count() + $this->keydbs()->count() + $this->dragonflies()->count() + $this->clickhouses()->count() + $this->services()->count(); + return $this->applications()->count() == 0 && + $this->redis()->count() == 0 && + $this->postgresqls()->count() == 0 && + $this->mysqls()->count() == 0 && + $this->keydbs()->count() == 0 && + $this->dragonflies()->count() == 0 && + $this->clickhouses()->count() == 0 && + $this->mariadbs()->count() == 0 && + $this->mongodbs()->count() == 0 && + $this->services()->count() == 0; } public function databases() diff --git a/app/Models/Server.php b/app/Models/Server.php index 0fcfd89ab..27c2b9b99 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Actions\Proxy\StartProxy; use App\Actions\Server\InstallDocker; use App\Actions\Server\StartSentinel; use App\Enums\ProxyTypes; @@ -26,22 +27,22 @@ use Symfony\Component\Yaml\Yaml; description: 'Server model', type: 'object', properties: [ - 'id' => ['type' => 'integer'], - 'uuid' => ['type' => 'string'], - 'name' => ['type' => 'string'], - 'description' => ['type' => 'string'], - 'ip' => ['type' => 'string'], - 'user' => ['type' => 'string'], - 'port' => ['type' => 'integer'], - 'proxy' => ['type' => 'object'], - 'high_disk_usage_notification_sent' => ['type' => 'boolean'], - 'unreachable_notification_sent' => ['type' => 'boolean'], - 'unreachable_count' => ['type' => 'integer'], - 'validation_logs' => ['type' => 'string'], - 'log_drain_notification_sent' => ['type' => 'boolean'], - 'swarm_cluster' => ['type' => 'string'], - 'delete_unused_volumes' => ['type' => 'boolean'], - 'delete_unused_networks' => ['type' => 'boolean'], + 'id' => ['type' => 'integer', 'description' => 'The server ID.'], + 'uuid' => ['type' => 'string', 'description' => 'The server UUID.'], + 'name' => ['type' => 'string', 'description' => 'The server name.'], + 'description' => ['type' => 'string', 'description' => 'The server description.'], + 'ip' => ['type' => 'string', 'description' => 'The IP address.'], + 'user' => ['type' => 'string', 'description' => 'The user.'], + 'port' => ['type' => 'integer', 'description' => 'The port number.'], + 'proxy' => ['type' => 'object', 'description' => 'The proxy configuration.'], + 'proxy_type' => ['type' => 'string', 'enum' => ['traefik', 'caddy', 'none'], 'description' => 'The proxy type.'], + 'high_disk_usage_notification_sent' => ['type' => 'boolean', 'description' => 'The flag to indicate if the high disk usage notification has been sent.'], + 'unreachable_notification_sent' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unreachable notification has been sent.'], + 'unreachable_count' => ['type' => 'integer', 'description' => 'The unreachable count for your server.'], + 'validation_logs' => ['type' => 'string', 'description' => 'The validation logs.'], + 'log_drain_notification_sent' => ['type' => 'boolean', 'description' => 'The flag to indicate if the log drain notification has been sent.'], + 'swarm_cluster' => ['type' => 'string', 'description' => 'The swarm cluster configuration.'], + 'settings' => ['$ref' => '#/components/schemas/ServerSetting'], ] )] @@ -64,7 +65,7 @@ class Server extends BaseModel $server->forceFill($payload); }); static::saved(function ($server) { - if ($server->privateKey->isDirty()) { + if ($server->privateKey?->isDirty()) { refresh_server_connection($server->privateKey); } }); @@ -462,7 +463,7 @@ $schema://$host { public function proxyPath() { - $base_path = config('coolify.base_config_path'); + $base_path = config('constants.coolify.base_config_path'); $proxyType = $this->proxyType(); $proxy_path = "$base_path/proxy"; // TODO: should use /traefik for already exisiting configurations? @@ -812,7 +813,7 @@ $schema://$host { { return Attribute::make( get: function ($value) { - return preg_replace('/[^0-9]/', '', $value); + return (int) preg_replace('/[^0-9]/', '', $value); } ); } @@ -986,7 +987,7 @@ $schema://$host { public function status(): bool { - ['uptime' => $uptime] = $this->validateConnection(false); + ['uptime' => $uptime] = $this->validateConnection(); if ($uptime === false) { foreach ($this->applications() as $application) { $application->status = 'exited'; @@ -1049,18 +1050,14 @@ $schema://$host { $this->team->notify(new Unreachable($this)); } - public function validateConnection(bool $isManualCheck = true, bool $justCheckingNewKey = false) + public function validateConnection(bool $justCheckingNewKey = false) { - config()->set('constants.ssh.mux_enabled', ! $isManualCheck); + config()->set('constants.ssh.mux_enabled', false); if ($this->skipServer()) { return ['uptime' => false, 'error' => 'Server skipped.']; } try { - // Make sure the private key is stored - if ($this->privateKey) { - $this->privateKey->storeInFileSystem(); - } instant_remote_process(['ls /'], $this); if ($this->settings->is_reachable === false) { $this->settings->is_reachable = true; @@ -1238,7 +1235,7 @@ $schema://$host { StartSentinel::run($this, true); } } catch (\Throwable $e) { - return handleError($e, $this); + return handleError($e); } } @@ -1251,4 +1248,25 @@ $schema://$host { { return instant_remote_process(['docker restart '.$containerName], $this, false); } + + public function changeProxy(string $proxyType, bool $async = true) + { + $validProxyTypes = collect(ProxyTypes::cases())->map(function ($proxyType) { + return str($proxyType->value)->lower(); + }); + if ($validProxyTypes->contains(str($proxyType)->lower())) { + $this->proxy->set('type', str($proxyType)->upper()); + $this->proxy->set('status', 'exited'); + $this->save(); + if ($this->proxySet()) { + if ($async) { + StartProxy::dispatch($this); + } else { + StartProxy::run($this); + } + } + } else { + throw new \Exception('Invalid proxy type.'); + } + } } diff --git a/app/Models/ServerSetting.php b/app/Models/ServerSetting.php index e72c8c444..e078372e2 100644 --- a/app/Models/ServerSetting.php +++ b/app/Models/ServerSetting.php @@ -45,6 +45,8 @@ use OpenApi\Attributes as OA; 'wildcard_domain' => ['type' => 'string'], 'created_at' => ['type' => 'string'], 'updated_at' => ['type' => 'string'], + 'delete_unused_volumes' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused volumes should be deleted.'], + 'delete_unused_networks' => ['type' => 'boolean', 'description' => 'The flag to indicate if the unused networks should be deleted.'], ] )] class ServerSetting extends Model @@ -64,10 +66,10 @@ class ServerSetting extends Model static::creating(function ($setting) { try { if (str($setting->sentinel_token)->isEmpty()) { - $setting->generateSentinelToken(save: false); + $setting->generateSentinelToken(save: false, ignoreEvent: true); } if (str($setting->sentinel_custom_url)->isEmpty()) { - $setting->generateSentinelUrl(save: false); + $setting->generateSentinelUrl(save: false, ignoreEvent: true); } } catch (\Throwable $e) { Log::error('Error creating server setting: '.$e->getMessage()); @@ -89,7 +91,7 @@ class ServerSetting extends Model }); } - public function generateSentinelToken(bool $save = true) + public function generateSentinelToken(bool $save = true, bool $ignoreEvent = false) { $data = [ 'server_uuid' => $this->server->uuid, @@ -98,13 +100,17 @@ class ServerSetting extends Model $encrypted = encrypt($token); $this->sentinel_token = $encrypted; if ($save) { - $this->save(); + if ($ignoreEvent) { + $this->saveQuietly(); + } else { + $this->save(); + } } return $token; } - public function generateSentinelUrl(bool $save = true) + public function generateSentinelUrl(bool $save = true, bool $ignoreEvent = false) { $domain = null; $settings = InstanceSettings::get(); @@ -119,7 +125,11 @@ class ServerSetting extends Model } $this->sentinel_custom_url = $domain; if ($save) { - $this->save(); + if ($ignoreEvent) { + $this->saveQuietly(); + } else { + $this->save(); + } } return $domain; diff --git a/app/Models/Service.php b/app/Models/Service.php index f88a23641..6d3d2024b 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -133,6 +133,11 @@ class Service extends BaseModel return $this->morphToMany(Tag::class, 'taggable'); } + public static function ownedByCurrentTeam() + { + return Service::whereRelation('environment.project.team', 'id', currentTeam()->id)->orderBy('name'); + } + public function getContainersToStop(): array { $containersToStop = []; @@ -1166,7 +1171,7 @@ class Service extends BaseModel $services = get_service_templates(); $service = data_get($services, str($this->name)->beforeLast('-')->value, []); - return data_get($service, 'documentation', config('constants.docs.base_url')); + return data_get($service, 'documentation', config('constants.urls.docs')); } public function applications() diff --git a/app/Models/ServiceApplication.php b/app/Models/ServiceApplication.php index 305913068..5cafc9042 100644 --- a/app/Models/ServiceApplication.php +++ b/app/Models/ServiceApplication.php @@ -37,6 +37,11 @@ class ServiceApplication extends BaseModel return ServiceApplication::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name'); } + public static function ownedByCurrentTeam() + { + return ServiceApplication::whereRelation('service.environment.project.team', 'id', currentTeam()->id)->orderBy('name'); + } + public function isRunning() { return str($this->status)->contains('running'); diff --git a/app/Models/ServiceDatabase.php b/app/Models/ServiceDatabase.php index 6641509dd..5fdd52637 100644 --- a/app/Models/ServiceDatabase.php +++ b/app/Models/ServiceDatabase.php @@ -24,6 +24,16 @@ class ServiceDatabase extends BaseModel }); } + public static function ownedByCurrentTeamAPI(int $teamId) + { + return ServiceDatabase::whereRelation('service.environment.project.team', 'id', $teamId)->orderBy('name'); + } + + public static function ownedByCurrentTeam() + { + return ServiceDatabase::whereRelation('service.environment.project.team', 'id', currentTeam()->id)->orderBy('name'); + } + public function restart() { $container_id = $this->name.'-'.$this->service->uuid; diff --git a/app/Models/Team.php b/app/Models/Team.php index 0377fa5c9..0181450ef 100644 --- a/app/Models/Team.php +++ b/app/Models/Team.php @@ -183,7 +183,7 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsNtfy { return Attribute::make( get: function () { - if (config('coolify.self_hosted') || $this->id === 0) { + if (config('constants.coolify.self_hosted') || $this->id === 0) { $subscription = 'self-hosted'; } else { $subscription = data_get($this, 'subscription'); @@ -268,8 +268,15 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsNtfy return $this->hasMany(S3Storage::class)->where('is_usable', true); } - public function trialEnded() + 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, @@ -278,16 +285,6 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsNtfy } } - public function trialEndedButSubscribed() - { - foreach ($this->servers as $server) { - $server->settings()->update([ - 'is_usable' => true, - 'is_reachable' => true, - ]); - } - } - public function isAnyNotificationEnabled() { if (isCloud()) { diff --git a/app/Models/TeamInvitation.php b/app/Models/TeamInvitation.php index 0f298a829..bc1a90d58 100644 --- a/app/Models/TeamInvitation.php +++ b/app/Models/TeamInvitation.php @@ -28,8 +28,8 @@ class TeamInvitation extends Model public function isValid() { $createdAt = $this->created_at; - $diff = $createdAt->diffInMinutes(now()); - if ($diff <= config('constants.invitation.link.expiration')) { + $diff = $createdAt->diffInDays(now()); + if ($diff <= config('constants.invitation.link.expiration_days')) { return true; } else { $this->delete(); diff --git a/app/Notifications/Application/DeploymentFailed.php b/app/Notifications/Application/DeploymentFailed.php index f3f8eaaf9..3f7a36293 100644 --- a/app/Notifications/Application/DeploymentFailed.php +++ b/app/Notifications/Application/DeploymentFailed.php @@ -4,18 +4,12 @@ namespace App\Notifications\Application; use App\Models\Application; use App\Models\ApplicationPreview; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class DeploymentFailed extends Notification implements ShouldQueue +class DeploymentFailed extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - public Application $application; public ?ApplicationPreview $preview = null; @@ -34,6 +28,7 @@ class DeploymentFailed extends Notification implements ShouldQueue public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null) { + $this->onQueue('high'); $this->application = $application; $this->deployment_uuid = $deployment_uuid; $this->preview = $preview; diff --git a/app/Notifications/Application/DeploymentSuccess.php b/app/Notifications/Application/DeploymentSuccess.php index 4de263ffd..ed28e509c 100644 --- a/app/Notifications/Application/DeploymentSuccess.php +++ b/app/Notifications/Application/DeploymentSuccess.php @@ -4,18 +4,12 @@ namespace App\Notifications\Application; use App\Models\Application; use App\Models\ApplicationPreview; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class DeploymentSuccess extends Notification implements ShouldQueue +class DeploymentSuccess extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - public Application $application; public ?ApplicationPreview $preview = null; @@ -34,6 +28,7 @@ class DeploymentSuccess extends Notification implements ShouldQueue public function __construct(Application $application, string $deployment_uuid, ?ApplicationPreview $preview = null) { + $this->onQueue('high'); $this->application = $application; $this->deployment_uuid = $deployment_uuid; $this->preview = $preview; diff --git a/app/Notifications/Application/StatusChanged.php b/app/Notifications/Application/StatusChanged.php index 36a1a74e0..7713d7346 100644 --- a/app/Notifications/Application/StatusChanged.php +++ b/app/Notifications/Application/StatusChanged.php @@ -3,18 +3,12 @@ namespace App\Notifications\Application; use App\Models\Application; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class StatusChanged extends Notification implements ShouldQueue +class StatusChanged extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - public string $resource_name; public string $project_uuid; @@ -27,6 +21,7 @@ class StatusChanged extends Notification implements ShouldQueue public function __construct(public Application $resource) { + $this->onQueue('high'); $this->resource_name = data_get($resource, 'name'); $this->project_uuid = data_get($resource, 'environment.project.uuid'); $this->environment_name = data_get($resource, 'environment.name'); diff --git a/app/Notifications/Channels/DiscordChannel.php b/app/Notifications/Channels/DiscordChannel.php index 86276fec9..df7040f8f 100644 --- a/app/Notifications/Channels/DiscordChannel.php +++ b/app/Notifications/Channels/DiscordChannel.php @@ -17,6 +17,6 @@ class DiscordChannel if (! $webhookUrl) { return; } - dispatch(new SendMessageToDiscordJob($message, $webhookUrl))->onQueue('high'); + SendMessageToDiscordJob::dispatch($message, $webhookUrl); } } diff --git a/app/Notifications/Channels/TelegramChannel.php b/app/Notifications/Channels/TelegramChannel.php index b3d4e384b..958c46c21 100644 --- a/app/Notifications/Channels/TelegramChannel.php +++ b/app/Notifications/Channels/TelegramChannel.php @@ -41,6 +41,6 @@ class TelegramChannel if (! $telegramToken || ! $chatId || ! $message) { return; } - dispatch(new SendMessageToTelegramJob($message, $buttons, $telegramToken, $chatId, $topicId))->onQueue('high'); + SendMessageToTelegramJob::dispatch($message, $buttons, $telegramToken, $chatId, $topicId); } } diff --git a/app/Notifications/Container/ContainerRestarted.php b/app/Notifications/Container/ContainerRestarted.php index ba4300291..b74aa2853 100644 --- a/app/Notifications/Container/ContainerRestarted.php +++ b/app/Notifications/Container/ContainerRestarted.php @@ -3,19 +3,16 @@ namespace App\Notifications\Container; use App\Models\Server; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class ContainerRestarted extends Notification implements ShouldQueue +class ContainerRestarted extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - - public function __construct(public string $name, public Server $server, public ?string $url = null) {} + public function __construct(public string $name, public Server $server, public ?string $url = null) + { + $this->onQueue('high'); + } public function via(object $notifiable): array { diff --git a/app/Notifications/Container/ContainerStopped.php b/app/Notifications/Container/ContainerStopped.php index 5a1ccf25d..1156159a3 100644 --- a/app/Notifications/Container/ContainerStopped.php +++ b/app/Notifications/Container/ContainerStopped.php @@ -3,19 +3,16 @@ namespace App\Notifications\Container; use App\Models\Server; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class ContainerStopped extends Notification implements ShouldQueue +class ContainerStopped extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - - public function __construct(public string $name, public Server $server, public ?string $url = null) {} + public function __construct(public string $name, public Server $server, public ?string $url = null) + { + $this->onQueue('high'); + } public function via(object $notifiable): array { diff --git a/app/Notifications/CustomEmailNotification.php b/app/Notifications/CustomEmailNotification.php new file mode 100644 index 000000000..c3c89b30f --- /dev/null +++ b/app/Notifications/CustomEmailNotification.php @@ -0,0 +1,18 @@ +onQueue('high'); $this->name = $database->name; $this->frequency = $backup->frequency; } diff --git a/app/Notifications/Database/BackupSuccess.php b/app/Notifications/Database/BackupSuccess.php index 0acfff4ca..f51058aa7 100644 --- a/app/Notifications/Database/BackupSuccess.php +++ b/app/Notifications/Database/BackupSuccess.php @@ -3,26 +3,20 @@ namespace App\Notifications\Database; use App\Models\ScheduledDatabaseBackup; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class BackupSuccess extends Notification implements ShouldQueue +class BackupSuccess extends CustomEmailNotification { - use Queueable; - - public $backoff = 10; - - public $tries = 3; - public string $name; public string $frequency; public function __construct(ScheduledDatabaseBackup $backup, public $database, public $database_name) { + $this->onQueue('high'); + $this->name = $database->name; $this->frequency = $backup->frequency; } diff --git a/app/Notifications/Dto/DiscordMessage.php b/app/Notifications/Dto/DiscordMessage.php index 856753dca..278bfd1b6 100644 --- a/app/Notifications/Dto/DiscordMessage.php +++ b/app/Notifications/Dto/DiscordMessage.php @@ -46,7 +46,7 @@ class DiscordMessage public function toPayload(): array { - $footerText = 'Coolify v'.config('version'); + $footerText = 'Coolify v'.config('constants.coolify.version'); if (isCloud()) { $footerText = 'Coolify Cloud'; } diff --git a/app/Notifications/Internal/GeneralNotification.php b/app/Notifications/Internal/GeneralNotification.php index e59c6ad3c..8b1e82569 100644 --- a/app/Notifications/Internal/GeneralNotification.php +++ b/app/Notifications/Internal/GeneralNotification.php @@ -18,6 +18,7 @@ class GeneralNotification extends Notification implements ShouldQueue public function __construct(public string $message) { + $this->onQueue('high'); } public function via(object $notifiable): array diff --git a/app/Notifications/Internal/GeneralNotification.php.orig b/app/Notifications/Internal/GeneralNotification.php.orig new file mode 100644 index 000000000..1b9714360 --- /dev/null +++ b/app/Notifications/Internal/GeneralNotification.php.orig @@ -0,0 +1,70 @@ +onQueue('high'); +>>>>>>> next + } + + public function via(object $notifiable): array + { + $channels = []; + $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); + $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); + $isNtfyEnabled = data_get($notifiable, 'ntfy_enabled'); + + if ($isDiscordEnabled) { + $channels[] = DiscordChannel::class; + } + if ($isTelegramEnabled) { + $channels[] = TelegramChannel::class; + } + if ($isNtfyEnabled) { + $channels[] = NtfyChannel::class; + } + + return $channels; + } + + public function toDiscord(): DiscordMessage + { + return new DiscordMessage( + title: 'Coolify: General Notification', + description: $this->message, + color: DiscordMessage::infoColor(), + ); + } + + public function toNtfy(): array + { + return [ + 'message' => $this->message, + 'buttons' => 'view, Go to your dashboard, '.base_url().';', + ]; + } + + public function toTelegram(): array + { + return [ + 'message' => $this->message, + ]; + } +} diff --git a/app/Notifications/ScheduledTask/TaskFailed.php b/app/Notifications/ScheduledTask/TaskFailed.php index 30e4d5735..6afa45082 100644 --- a/app/Notifications/ScheduledTask/TaskFailed.php +++ b/app/Notifications/ScheduledTask/TaskFailed.php @@ -3,24 +3,17 @@ namespace App\Notifications\ScheduledTask; use App\Models\ScheduledTask; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class TaskFailed extends Notification implements ShouldQueue +class TaskFailed extends CustomEmailNotification { - use Queueable; - - public $backoff = 10; - - public $tries = 2; - public ?string $url = null; public function __construct(public ScheduledTask $task, public string $output) { + $this->onQueue('high'); if ($task->application) { $this->url = $task->application->failedTaskLink($task->uuid); } elseif ($task->service) { diff --git a/app/Notifications/Server/DockerCleanup.php b/app/Notifications/Server/DockerCleanup.php index 4b52fc459..a08263644 100644 --- a/app/Notifications/Server/DockerCleanup.php +++ b/app/Notifications/Server/DockerCleanup.php @@ -6,18 +6,15 @@ use App\Models\Server; use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\TelegramChannel; use App\Notifications\Channels\NtfyChannel; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; -use Illuminate\Notifications\Notification; -class DockerCleanup extends Notification implements ShouldQueue +class DockerCleanup extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - - public function __construct(public Server $server, public string $message) {} + public function __construct(public Server $server, public string $message) + { + $this->onQueue('high'); + } public function via(object $notifiable): array { diff --git a/app/Notifications/Server/DockerCleanup.php.orig b/app/Notifications/Server/DockerCleanup.php.orig new file mode 100644 index 000000000..1fc91089e --- /dev/null +++ b/app/Notifications/Server/DockerCleanup.php.orig @@ -0,0 +1,83 @@ +>>>>>> next +use App\Notifications\Dto\DiscordMessage; + +class DockerCleanup extends CustomEmailNotification +{ + public function __construct(public Server $server, public string $message) + { + $this->onQueue('high'); + } + + public function via(object $notifiable): array + { + $channels = []; + // $isEmailEnabled = isEmailEnabled($notifiable); + $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); + $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); + $isNtfyEnabled = data_get($notifiable, 'ntfy_enabled'); + + if ($isDiscordEnabled) { + $channels[] = DiscordChannel::class; + } + // if ($isEmailEnabled) { + // $channels[] = EmailChannel::class; + // } + if ($isTelegramEnabled) { + $channels[] = TelegramChannel::class; + } + + if ($isNtfyEnabled) { + $channels[] = NtfyChannel::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 toNtfy() + { + return [ + 'title' => "Coolify: Server '{$this->server->name}' cleanup job done!", + 'message' => "Coolify: Server '{$this->server->name}' cleanup job done!\n\n{$this->message}", + 'emoji' => 'wastebasket', + ]; + } + + public function toTelegram(): array + { + return [ + 'message' => "Coolify: Server '{$this->server->name}' cleanup job done!\n\n{$this->message}", + ]; + } +} diff --git a/app/Notifications/Server/ForceDisabled.php b/app/Notifications/Server/ForceDisabled.php index d00d7e403..b15b1f87b 100644 --- a/app/Notifications/Server/ForceDisabled.php +++ b/app/Notifications/Server/ForceDisabled.php @@ -7,13 +7,11 @@ use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\TelegramChannel; use App\Notifications\Channels\NtfyChannel; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class ForceDisabled extends Notification implements ShouldQueue +class ForceDisabled extends CustomEmailNotification { use Queueable; @@ -21,6 +19,7 @@ class ForceDisabled extends Notification implements ShouldQueue public function __construct(public Server $server) { + $this->onQueue('high'); } public function via(object $notifiable): array diff --git a/app/Notifications/Server/ForceDisabled.php.orig b/app/Notifications/Server/ForceDisabled.php.orig new file mode 100644 index 000000000..3ea3e816f --- /dev/null +++ b/app/Notifications/Server/ForceDisabled.php.orig @@ -0,0 +1,97 @@ +>>>>>> next +use App\Notifications\Dto\DiscordMessage; +use Illuminate\Notifications\Messages\MailMessage; + +class ForceDisabled extends CustomEmailNotification +{ +<<<<<<< HEAD + use Queueable; + + public $tries = 1; + + public function __construct(public Server $server) + { +======= + public function __construct(public Server $server) + { + $this->onQueue('high'); +>>>>>>> next + } + + public function via(object $notifiable): array + { + $channels = []; + $isEmailEnabled = isEmailEnabled($notifiable); + $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); + $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); + $isNtfyEnabled = data_get($notifiable, 'ntfy_enabled'); + + if ($isDiscordEnabled) { + $channels[] = DiscordChannel::class; + } + if ($isEmailEnabled) { + $channels[] = EmailChannel::class; + } + if ($isTelegramEnabled) { + $channels[] = TelegramChannel::class; + } + if ($isNtfyEnabled) { + $channels[] = NtfyChannel::class; + } + + return $channels; + } + + public function toMail(): MailMessage + { + $mail = new MailMessage; + $mail->subject("Coolify: Server ({$this->server->name}) disabled because it is not paid!"); + $mail->view('emails.server-force-disabled', [ + 'name' => $this->server->name, + ]); + + return $mail; + } + + public function toNtfy(): array + { + return [ + 'title' => "Coolify: Server ({$this->server->name}) disabled because it is not paid!", + 'message' => "All automations and integrations are stopped.\nPlease update your subscription to enable the server again [here](https://app.coolify.io/subsciprtions", + 'buttons' => 'view, Update subscription, '.base_url().'/subscriptions;', + 'emoji' => 'stop_sign', + ]; + } + + public function toDiscord(): DiscordMessage + { + $message = new DiscordMessage( + title: ':cross_mark: Server disabled', + description: "Server ({$this->server->name}) disabled because it is not paid!", + color: DiscordMessage::errorColor(), + ); + + $message->addField('Please update your subscription to enable the server again!', '[Link](https://app.coolify.io/subscriptions)'); + + return $message; + } + + public function toTelegram(): array + { + return [ + '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).", + ]; + } +} diff --git a/app/Notifications/Server/ForceEnabled.php b/app/Notifications/Server/ForceEnabled.php index 46ec781d6..2e2993909 100644 --- a/app/Notifications/Server/ForceEnabled.php +++ b/app/Notifications/Server/ForceEnabled.php @@ -7,19 +7,16 @@ use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\NtfyChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class ForceEnabled extends Notification implements ShouldQueue +class ForceEnabled extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - - public function __construct(public Server $server) {} + public function __construct(public Server $server) + { + $this->onQueue('high'); + } public function via(object $notifiable): array { diff --git a/app/Notifications/Server/HighDiskUsage.php b/app/Notifications/Server/HighDiskUsage.php index 00dee61cb..6cd6cc664 100644 --- a/app/Notifications/Server/HighDiskUsage.php +++ b/app/Notifications/Server/HighDiskUsage.php @@ -3,20 +3,19 @@ namespace App\Notifications\Server; use App\Models\Server; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; use App\Notifications\Channels\NtfyChannel; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class HighDiskUsage extends Notification implements ShouldQueue +class HighDiskUsage extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - - public function __construct(public Server $server, public int $disk_usage, public int $server_disk_usage_notification_threshold) {} + public function __construct(public Server $server, public int $disk_usage, public int $server_disk_usage_notification_threshold) + { + $this->onQueue('high'); + } public function via(object $notifiable): array { diff --git a/app/Notifications/Server/HighDiskUsage.php.orig b/app/Notifications/Server/HighDiskUsage.php.orig new file mode 100644 index 000000000..006b17625 --- /dev/null +++ b/app/Notifications/Server/HighDiskUsage.php.orig @@ -0,0 +1,73 @@ +>>>>>> next +use Illuminate\Notifications\Messages\MailMessage; + +class HighDiskUsage extends CustomEmailNotification +{ + public function __construct(public Server $server, public int $disk_usage, public int $server_disk_usage_notification_threshold) + { + $this->onQueue('high'); + } + + public function via(object $notifiable): array + { + return setNotificationChannels($notifiable, 'server_disk_usage'); + } + + 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->server_disk_usage_notification_threshold, + ]); + + return $mail; + } + + public function toDiscord(): DiscordMessage + { + $message = new DiscordMessage( + title: ':cross_mark: High disk usage detected', + description: "Server '{$this->server->name}' high disk usage detected!", + color: DiscordMessage::errorColor(), + isCritical: true, + ); + + $message->addField('Disk usage', "{$this->disk_usage}%", true); + $message->addField('Threshold', "{$this->server_disk_usage_notification_threshold}%", true); + $message->addField('What to do?', '[Link](https://coolify.io/docs/knowledge-base/server/automated-cleanup)', true); + $message->addField('Change Settings', '[Threshold]('.base_url().'/server/'.$this->server->uuid.'#advanced) | [Notification]('.base_url().'/notifications/discord)'); + + return $message; + } + + public function toNtfy(): array + { + return [ + 'title' => "Coolify: Server '{$this->server->name}' high disk usage detected!", + 'message' => "Disk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.", + 'buttons' => 'view, Go to your dashboard, '.base_url().';', + ]; + } + + public function toTelegram(): array + { + return [ + '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.", + ]; + } +} diff --git a/app/Notifications/Server/Reachable.php b/app/Notifications/Server/Reachable.php index 5bde8987a..09ee8cf82 100644 --- a/app/Notifications/Server/Reachable.php +++ b/app/Notifications/Server/Reachable.php @@ -7,22 +7,17 @@ use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\TelegramChannel; use App\Notifications\Channels\NtfyChannel; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class Reachable extends Notification implements ShouldQueue +class Reachable extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - protected bool $isRateLimited = false; public function __construct(public Server $server) { + $this->onQueue('high'); $this->isRateLimited = isEmailRateLimited( limiterKey: 'server-reachable:'.$this->server->id, ); diff --git a/app/Notifications/Server/Reachable.php.orig b/app/Notifications/Server/Reachable.php.orig new file mode 100644 index 000000000..7889db9c1 --- /dev/null +++ b/app/Notifications/Server/Reachable.php.orig @@ -0,0 +1,100 @@ +>>>>>> next +use App\Notifications\Dto\DiscordMessage; +use Illuminate\Notifications\Messages\MailMessage; + +class Reachable extends CustomEmailNotification +{ + protected bool $isRateLimited = false; + + public function __construct(public Server $server) + { + $this->onQueue('high'); + $this->isRateLimited = isEmailRateLimited( + limiterKey: 'server-reachable:'.$this->server->id, + ); + } + + public function via(object $notifiable): array + { + if ($this->isRateLimited) { + return []; + } + + $channels = []; + $isEmailEnabled = isEmailEnabled($notifiable); + $isDiscordEnabled = data_get($notifiable, 'discord_enabled'); + $isNtfyEnabled = data_get($notifiable, 'ntfy_enabled'); + $isTelegramEnabled = data_get($notifiable, 'telegram_enabled'); + + if ($isDiscordEnabled) { + $channels[] = DiscordChannel::class; + } + if ($isEmailEnabled) { + $channels[] = EmailChannel::class; + } + if ($isTelegramEnabled) { + $channels[] = TelegramChannel::class; + } + if ($isNtfyEnabled) { + $channels[] = NtfyChannel::class; + } + + return $channels; + } + + public function toMail(): MailMessage + { + $mail = new MailMessage; + $mail->subject("Coolify: Server ({$this->server->name}) revived."); + $mail->view('emails.server-revived', [ + 'name' => $this->server->name, + ]); + + return $mail; + } + + public function toDiscord(): DiscordMessage + { + return new DiscordMessage( + + return $message; + } + + + public function toDiscord(): DiscordMessage + { + return new DiscordMessage( + title: ":white_check_mark: Server '{$this->server->name}' revived", + description: 'All automations & integrations are turned on again!', + color: DiscordMessage::successColor(), + ); + } + + public function toNtfy(): array + { + return [ + 'title' => "Coolify: Server '{$this->server->name}' revived.", + 'message' => "All automations & integrations are turned on again!", + 'emoji' => 'white_check_mark', + ]; + } + + public function toTelegram(): array + { + return [ + 'message' => "Coolify: Server '{$this->server->name}' revived. All automations & integrations are turned on again!", + ]; + } +} diff --git a/app/Notifications/Server/Unreachable.php b/app/Notifications/Server/Unreachable.php index a9bb4ac8f..f186570a4 100644 --- a/app/Notifications/Server/Unreachable.php +++ b/app/Notifications/Server/Unreachable.php @@ -7,22 +7,17 @@ use App\Notifications\Channels\DiscordChannel; use App\Notifications\Channels\EmailChannel; use App\Notifications\Channels\NtfyChannel; use App\Notifications\Channels\TelegramChannel; +use App\Notifications\CustomEmailNotification; use App\Notifications\Dto\DiscordMessage; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class Unreachable extends Notification implements ShouldQueue +class Unreachable extends CustomEmailNotification { - use Queueable; - - public $tries = 1; - protected bool $isRateLimited = false; public function __construct(public Server $server) { + $this->onQueue('high'); $this->isRateLimited = isEmailRateLimited( limiterKey: 'server-unreachable:'.$this->server->id, ); diff --git a/app/Notifications/Test.php b/app/Notifications/Test.php index 4fea2099d..f4d82560a 100644 --- a/app/Notifications/Test.php +++ b/app/Notifications/Test.php @@ -15,7 +15,10 @@ class Test extends Notification implements ShouldQueue public $tries = 5; - public function __construct(public ?string $emails = null) {} + public function __construct(public ?string $emails = null) + { + $this->onQueue('high'); + } public function via(object $notifiable): array { diff --git a/app/Notifications/TransactionalEmails/InvitationLink.php b/app/Notifications/TransactionalEmails/InvitationLink.php index 6da2a6fcc..30ace99dc 100644 --- a/app/Notifications/TransactionalEmails/InvitationLink.php +++ b/app/Notifications/TransactionalEmails/InvitationLink.php @@ -6,23 +6,20 @@ use App\Models\Team; use App\Models\TeamInvitation; use App\Models\User; use App\Notifications\Channels\TransactionalEmailChannel; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; +use App\Notifications\CustomEmailNotification; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class InvitationLink extends Notification implements ShouldQueue +class InvitationLink extends CustomEmailNotification { - use Queueable; - - public $tries = 5; - public function via(): array { return [TransactionalEmailChannel::class]; } - public function __construct(public User $user) {} + public function __construct(public User $user) + { + $this->onQueue('high'); + } public function toMail(): MailMessage { diff --git a/app/Notifications/TransactionalEmails/Test.php b/app/Notifications/TransactionalEmails/Test.php index 64883a58e..eeb32a254 100644 --- a/app/Notifications/TransactionalEmails/Test.php +++ b/app/Notifications/TransactionalEmails/Test.php @@ -3,18 +3,15 @@ namespace App\Notifications\TransactionalEmails; use App\Notifications\Channels\EmailChannel; -use Illuminate\Bus\Queueable; -use Illuminate\Contracts\Queue\ShouldQueue; +use App\Notifications\CustomEmailNotification; use Illuminate\Notifications\Messages\MailMessage; -use Illuminate\Notifications\Notification; -class Test extends Notification implements ShouldQueue +class Test extends CustomEmailNotification { - use Queueable; - - public $tries = 5; - - public function __construct(public string $emails) {} + public function __construct(public string $emails) + { + $this->onQueue('high'); + } public function via(): array { diff --git a/app/Providers/FortifyServiceProvider.php b/app/Providers/FortifyServiceProvider.php index e8784bab3..bbbf48345 100644 --- a/app/Providers/FortifyServiceProvider.php +++ b/app/Providers/FortifyServiceProvider.php @@ -50,7 +50,7 @@ class FortifyServiceProvider extends ServiceProvider if (! $settings->is_registration_enabled) { return redirect()->route('login'); } - if (config('coolify.waitlist')) { + if (config('constants.waitlist.enabled')) { return redirect()->route('waitlist.index'); } else { return view('auth.register', [ diff --git a/app/View/Components/Forms/Checkbox.php b/app/View/Components/Forms/Checkbox.php index 414dbf2ae..0bdebe7e4 100644 --- a/app/View/Components/Forms/Checkbox.php +++ b/app/View/Components/Forms/Checkbox.php @@ -17,11 +17,14 @@ class Checkbox extends Component public ?string $value = null, public ?string $label = null, public ?string $helper = null, + public string|bool|null $checked = false, public string|bool $instantSave = false, public bool $disabled = false, public string $defaultClass = 'dark:border-neutral-700 text-coolgray-400 focus:ring-warning dark:bg-coolgray-100 rounded cursor-pointer dark:disabled:bg-base dark:disabled:cursor-not-allowed', ) { - // + if ($this->disabled) { + $this->defaultClass .= ' opacity-40'; + } } /** diff --git a/bootstrap/getVersion.php b/bootstrap/getVersion.php index a8329a319..8fa27a332 100644 --- a/bootstrap/getVersion.php +++ b/bootstrap/getVersion.php @@ -1,4 +1,10 @@ id, - ))->onQueue('high'); + ); } elseif (next_queuable($server_id, $application_id)) { - dispatch(new ApplicationDeploymentJob( + ApplicationDeploymentJob::dispatch( application_deployment_queue_id: $deployment->id, - ))->onQueue('high'); + ); } } function force_start_deployment(ApplicationDeploymentQueue $deployment) @@ -59,9 +59,9 @@ function force_start_deployment(ApplicationDeploymentQueue $deployment) 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, ]); - dispatch(new ApplicationDeploymentJob( + ApplicationDeploymentJob::dispatch( application_deployment_queue_id: $deployment->id, - ))->onQueue('high'); + ); } function queue_next_deployment(Application $application) { @@ -72,9 +72,9 @@ function queue_next_deployment(Application $application) 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, ]); - dispatch(new ApplicationDeploymentJob( + ApplicationDeploymentJob::dispatch( application_deployment_queue_id: $next_found->id, - ))->onQueue('high'); + ); } } @@ -113,9 +113,9 @@ function next_after_cancel(?Server $server = null) 'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, ]); - dispatch(new ApplicationDeploymentJob( + ApplicationDeploymentJob::dispatch( application_deployment_queue_id: $next->id, - ))->onQueue('high'); + ); } break; } diff --git a/bootstrap/helpers/docker.php b/bootstrap/helpers/docker.php index 2e583b94d..8dd01a162 100644 --- a/bootstrap/helpers/docker.php +++ b/bootstrap/helpers/docker.php @@ -109,7 +109,8 @@ function format_docker_envs_to_json($rawOutput) function checkMinimumDockerEngineVersion($dockerVersion) { $majorDockerVersion = str($dockerVersion)->before('.')->value(); - if ($majorDockerVersion <= 22) { + $requiredDockerVersion = str(config('constants.docker.minimum_required_version'))->before('.')->value(); + if ($majorDockerVersion < $requiredDockerVersion) { $dockerVersion = null; } @@ -191,7 +192,7 @@ function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'applica { $labels = collect([]); $labels->push('coolify.managed=true'); - $labels->push('coolify.version='.config('version')); + $labels->push('coolify.version='.config('constants.coolify.version')); $labels->push('coolify.'.$type.'Id='.$id); $labels->push("coolify.type=$type"); $labels->push('coolify.name='.$name); @@ -225,16 +226,18 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource) case $type?->contains('minio'): $MINIO_BROWSER_REDIRECT_URL = $variables->where('key', 'MINIO_BROWSER_REDIRECT_URL')->first(); $MINIO_SERVER_URL = $variables->where('key', 'MINIO_SERVER_URL')->first(); + if (is_null($MINIO_BROWSER_REDIRECT_URL) || is_null($MINIO_SERVER_URL)) { - return $payload; + return collect([]); } - if (is_null($MINIO_BROWSER_REDIRECT_URL?->value)) { - $MINIO_BROWSER_REDIRECT_URL?->update([ + + if (str($MINIO_BROWSER_REDIRECT_URL->value ?? '')->isEmpty()) { + $MINIO_BROWSER_REDIRECT_URL->update([ 'value' => generateFqdn($server, 'console-'.$uuid, true), ]); } - if (is_null($MINIO_SERVER_URL?->value)) { - $MINIO_SERVER_URL?->update([ + if (str($MINIO_SERVER_URL->value ?? '')->isEmpty()) { + $MINIO_SERVER_URL->update([ 'value' => generateFqdn($server, 'minio-'.$uuid, true), ]); } @@ -246,16 +249,18 @@ function generateServiceSpecificFqdns(ServiceApplication|Application $resource) case $type?->contains('logto'): $LOGTO_ENDPOINT = $variables->where('key', 'LOGTO_ENDPOINT')->first(); $LOGTO_ADMIN_ENDPOINT = $variables->where('key', 'LOGTO_ADMIN_ENDPOINT')->first(); + if (is_null($LOGTO_ENDPOINT) || is_null($LOGTO_ADMIN_ENDPOINT)) { - return $payload; + return collect([]); } - if (is_null($LOGTO_ENDPOINT?->value)) { - $LOGTO_ENDPOINT?->update([ + + if (str($LOGTO_ENDPOINT->value ?? '')->isEmpty()) { + $LOGTO_ENDPOINT->update([ 'value' => generateFqdn($server, 'logto-'.$uuid), ]); } - if (is_null($LOGTO_ADMIN_ENDPOINT?->value)) { - $LOGTO_ADMIN_ENDPOINT?->update([ + if (str($LOGTO_ADMIN_ENDPOINT->value ?? '')->isEmpty()) { + $LOGTO_ADMIN_ENDPOINT->update([ 'value' => generateFqdn($server, 'logto-admin-'.$uuid), ]); } @@ -283,6 +288,10 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, $host_without_www = str($host)->replace('www.', ''); $schema = $url->getScheme(); $port = $url->getPort(); + $handle = "handle_path"; + if ( ! $is_stripprefix_enabled){ + $handle = "handle"; + } if (is_null($port) && ! is_null($onlyPort)) { $port = $onlyPort; } @@ -293,12 +302,13 @@ function fqdnLabelsForCaddy(string $network, string $uuid, Collection $domains, $labels->push("caddy_{$loop}.header=-Server"); $labels->push("caddy_{$loop}.try_files={path} /index.html /index.php"); + if ($port) { - $labels->push("caddy_{$loop}.handle_path.{$loop}_reverse_proxy={{upstreams $port}}"); + $labels->push("caddy_{$loop}.{$handle}.{$loop}_reverse_proxy={{upstreams $port}}"); } else { - $labels->push("caddy_{$loop}.handle_path.{$loop}_reverse_proxy={{upstreams}}"); + $labels->push("caddy_{$loop}.{$handle}.{$loop}_reverse_proxy={{upstreams}}"); } - $labels->push("caddy_{$loop}.handle_path={$path}*"); + $labels->push("caddy_{$loop}.{$handle}={$path}*"); if ($is_gzip_enabled) { $labels->push("caddy_{$loop}.encode=zstd gzip"); } @@ -359,8 +369,11 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $https_label = "https-{$loop}-{$uuid}-{$service_name}"; } if (str($image)->contains('ghost')) { - $labels->push("traefik.http.middlewares.redir-ghost.redirectregex.regex=^{$path}/(.*)"); - $labels->push('traefik.http.middlewares.redir-ghost.redirectregex.replacement=/$1'); + $labels->push("traefik.http.middlewares.redir-ghost-{$uuid}.redirectregex.regex=^{$path}/(.*)"); + $labels->push("traefik.http.middlewares.redir-ghost-{$uuid}.redirectregex.replacement=/$1"); + $labels->push("caddy_{$loop}.handle_path.{$loop}_redir-ghost-{$uuid}.handler=rewrite"); + $labels->push("caddy_{$loop}.handle_path.{$loop}_redir-ghost-{$uuid}.rewrite.regexp=^{$path}/(.*)"); + $labels->push("caddy_{$loop}.handle_path.{$loop}_redir-ghost-{$uuid}.rewrite.replacement=/$1"); } $to_www_name = "{$loop}-{$uuid}-to-www"; @@ -394,7 +407,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $middlewares->push('gzip'); } if (str($image)->contains('ghost')) { - $middlewares->push('redir-ghost'); + $middlewares->push("redir-ghost-{$uuid}"); } if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { $labels = $labels->merge($redirect_to_non_www); @@ -417,7 +430,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $middlewares->push('gzip'); } if (str($image)->contains('ghost')) { - $middlewares->push('redir-ghost'); + $middlewares->push("redir-ghost-{$uuid}"); } if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { $labels = $labels->merge($redirect_to_non_www); @@ -466,7 +479,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $middlewares->push('gzip'); } if (str($image)->contains('ghost')) { - $middlewares->push('redir-ghost'); + $middlewares->push("redir-ghost-{$uuid}"); } if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { $labels = $labels->merge($redirect_to_non_www); @@ -489,7 +502,7 @@ function fqdnLabelsForTraefik(string $uuid, Collection $domains, bool $is_force_ $middlewares->push('gzip'); } if (str($image)->contains('ghost')) { - $middlewares->push('redir-ghost'); + $middlewares->push("redir-ghost-{$uuid}"); } if ($redirect_direction === 'non-www' && str($host)->startsWith('www.')) { $labels = $labels->merge($redirect_to_non_www); diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 8eced0b77..e27dd8aad 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -7,6 +7,7 @@ use App\Models\Application; use App\Models\ApplicationDeploymentQueue; use App\Models\ApplicationPreview; use App\Models\EnvironmentVariable; +use App\Models\GithubApp; use App\Models\InstanceSettings; use App\Models\LocalFileVolume; use App\Models\LocalPersistentVolume; @@ -359,7 +360,7 @@ function isDev(): bool function isCloud(): bool { - return ! config('coolify.self_hosted'); + return ! config('constants.coolify.self_hosted'); } function translate_cron_expression($expression_to_validate): string @@ -385,6 +386,11 @@ function validate_cron_expression($expression_to_validate): bool return $isValid; } + +function validate_timezone(string $timezone): bool +{ + return in_array($timezone, timezone_identifiers_list()); +} function send_internal_notification(string $message): void { try { @@ -941,6 +947,15 @@ function generateEnvValue(string $command, Service|Application|null $service = n case 'REALBASE64_32': $generatedValue = base64_encode(Str::random(32)); break; + case 'HEX_32': + $generatedValue = bin2hex(Str::random(32)); + break; + case 'HEX_64': + $generatedValue = bin2hex(Str::random(64)); + break; + case 'HEX_128': + $generatedValue = bin2hex(Str::random(128)); + break; case 'USER': $generatedValue = Str::random(16); break; @@ -995,7 +1010,7 @@ function generateEnvValue(string $command, Service|Application|null $service = n function getRealtime() { - $envDefined = env('PUSHER_PORT'); + $envDefined = config('constants.pusher.port'); if (empty($envDefined)) { $url = Url::fromString(Request::getSchemeAndHttpHost()); $port = $url->getPort(); @@ -4069,3 +4084,83 @@ function isEmailRateLimited(string $limiterKey, int $decaySeconds = 3600, ?calla return $rateLimited; } + +function defaultNginxConfiguration(): string +{ + return 'server { + location / { + root /usr/share/nginx/html; + index index.html index.htm; + try_files $uri $uri.html $uri/index.html $uri/index.htm $uri/ /index.html /index.htm =404; + } + + error_page 500 502 503 504 /50x.html; + location = /50x.html { + root /usr/share/nginx/html; + try_files $uri @redirect_to_index; + internal; + } + + error_page 404 = @handle_404; + + location @handle_404 { + root /usr/share/nginx/html; + try_files /404.html @redirect_to_index; + internal; + } + + location @redirect_to_index { + return 302 /; + } +}'; +} + +function convertGitUrl(string $gitRepository, string $deploymentType, ?GithubApp $source = null): array +{ + $repository = $gitRepository; + $providerInfo = [ + 'host' => null, + 'user' => 'git', + 'port' => 22, + 'repository' => $gitRepository, + ]; + $sshMatches = []; + $matches = []; + + // Let's try and parse the string to detect if it's a valid SSH string or not + preg_match('/((.*?)\:\/\/)?(.*@.*:.*)/', $gitRepository, $sshMatches); + + if ($deploymentType === 'deploy_key' && empty($sshMatches) && $source) { + // If this happens, the user may have provided an HTTP URL when they needed an SSH one + // Let's try and fix that for known Git providers + switch ($source->getMorphClass()) { + case \App\Models\GithubApp::class: + $providerInfo['host'] = Url::fromString($source->html_url)->getHost(); + $providerInfo['port'] = $source->custom_port; + $providerInfo['user'] = $source->custom_user; + break; + } + if (! empty($providerInfo['host'])) { + // Until we do not support more providers with App (like GithubApp), this will be always true, port will be 22 + if ($providerInfo['port'] === 22) { + $repository = "{$providerInfo['user']}@{$providerInfo['host']}:{$providerInfo['repository']}"; + } else { + $repository = "ssh://{$providerInfo['user']}@{$providerInfo['host']}:{$providerInfo['port']}/{$providerInfo['repository']}"; + } + } + } + + preg_match('/(?<=:)\d+(?=\/)/', $gitRepository, $matches); + + if (count($matches) === 1) { + $providerInfo['port'] = $matches[0]; + $gitHost = str($gitRepository)->before(':'); + $gitRepo = str($gitRepository)->after('/'); + $repository = "$gitHost:$gitRepo"; + } + + return [ + 'repository' => $repository, + 'port' => $providerInfo['port'], + ]; +} diff --git a/composer.json b/composer.json index 2bae1149c..694bad882 100644 --- a/composer.json +++ b/composer.json @@ -12,14 +12,15 @@ ], "require": { "php": "^8.2", + "3sidedcube/laravel-redoc": "^1.0", "danharrin/livewire-rate-limiting": "^1.1", - "doctrine/dbal": "^3.6", + "doctrine/dbal": "^4.2", "guzzlehttp/guzzle": "^7.5.0", "laravel/fortify": "^1.16.0", - "laravel/framework": "^11", + "laravel/framework": "^11.0", "laravel/horizon": "^5.29.1", "laravel/pail": "^1.1", - "laravel/prompts": "^0.1.6", + "laravel/prompts": "^0.1.18|^0.2.0|^0.3.0", "laravel/sanctum": "^4.0", "laravel/socialite": "^5.14.0", "laravel/tinker": "^2.8.1", @@ -27,7 +28,7 @@ "lcobucci/jwt": "^5.0.0", "league/flysystem-aws-s3-v3": "^3.0", "league/flysystem-sftp-v3": "^3.0", - "livewire/livewire": "3.4.9", + "livewire/livewire": "^3.5", "log1x/laravel-webfonts": "^1.0", "lorisleiva/laravel-actions": "^2.7", "nubs/random-name-generator": "^2.2", @@ -36,17 +37,17 @@ "poliander/cron": "^3.0", "purplepixie/phpdns": "^2.1", "pusher/pusher-php-server": "^7.2", - "resend/resend-laravel": "^0.13.0", + "resend/resend-laravel": "^0.15.0", "sentry/sentry-laravel": "^4.6", "socialiteproviders/microsoft-azure": "^5.1", "spatie/laravel-activitylog": "^4.7.3", - "spatie/laravel-data": "^3.4.3", - "spatie/laravel-ray": "^1.32.4", + "spatie/laravel-data": "^4.11", + "spatie/laravel-ray": "^1.37", "spatie/laravel-schemaless-attributes": "^2.4", "spatie/url": "^2.2", - "stripe/stripe-php": "^12.0", - "symfony/yaml": "^6.2", - "visus/cuid2": "^2.0.0", + "stripe/stripe-php": "^16.2.0", + "symfony/yaml": "^7.1.6", + "visus/cuid2": "^4.1.0", "yosymfony/toml": "^1.0", "zircote/swagger-php": "^4.10" }, @@ -58,12 +59,12 @@ "laravel/telescope": "^5.2", "mockery/mockery": "^1.5.1", "nunomaduro/collision": "^8.1", - "pestphp/pest": "^2.16", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^10.0.19", - "serversideup/spin": "^1.1.0", + "pestphp/pest": "^3.5", + "phpstan/phpstan": "^1.12.10", + "phpunit/phpunit": "^11.4", + "serversideup/spin": "^2.3", "spatie/laravel-ignition": "^2.1.0", - "symfony/http-client": "^6.2" + "symfony/http-client": "^7.1" }, "minimum-stability": "stable", "prefer-stable": true, @@ -119,4 +120,4 @@ "@php artisan key:generate --ansi" ] } -} \ No newline at end of file +} diff --git a/composer.lock b/composer.lock index fb0dcd018..8ea0d9a5a 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,66 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "3f2342fe6b1ba920c8875f8a8fe41962", + "content-hash": "f50de759f43a3eefb58ce9ebbb02d33b", "packages": [ + { + "name": "3sidedcube/laravel-redoc", + "version": "v1.0.1", + "source": { + "type": "git", + "url": "https://github.com/3sidedcube/laravel-redoc.git", + "reference": "c33a563885dcdf1e0f623df5a56c106d130261da" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/3sidedcube/laravel-redoc/zipball/c33a563885dcdf1e0f623df5a56c106d130261da", + "reference": "c33a563885dcdf1e0f623df5a56c106d130261da", + "shasum": "" + }, + "require": { + "illuminate/routing": "^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^8.0|^9.0|^10.0|^11.0", + "php": "^7.4|^8.0|^8.1|^8.2|^8.3" + }, + "require-dev": { + "friendsofphp/php-cs-fixer": "^3.3", + "orchestra/testbench": "^6.0|^7.0|^8.0|^9.0" + }, + "type": "library", + "extra": { + "laravel": { + "providers": [ + "ThreeSidedCube\\LaravelRedoc\\RedocServiceProvider" + ] + } + }, + "autoload": { + "psr-4": { + "ThreeSidedCube\\LaravelRedoc\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ben Sherred", + "role": "Developer" + } + ], + "description": "A lightweight package for rendering API documentation using OpenAPI and Redoc.", + "homepage": "https://github.com/3sidedcube/laravel-redoc", + "keywords": [ + "3sidedcube", + "laravel-redoc" + ], + "support": { + "issues": "https://github.com/3sidedcube/laravel-redoc/issues", + "source": "https://github.com/3sidedcube/laravel-redoc/tree/v1.0.1" + }, + "time": "2024-05-20T11:37:55+00:00" + }, { "name": "amphp/amp", "version": "v3.0.2", @@ -867,16 +925,16 @@ }, { "name": "aws/aws-crt-php", - "version": "v1.2.6", + "version": "v1.2.7", "source": { "type": "git", "url": "https://github.com/awslabs/aws-crt-php.git", - "reference": "a63485b65b6b3367039306496d49737cf1995408" + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/a63485b65b6b3367039306496d49737cf1995408", - "reference": "a63485b65b6b3367039306496d49737cf1995408", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/d71d9906c7bb63a28295447ba12e74723bd3730e", + "reference": "d71d9906c7bb63a28295447ba12e74723bd3730e", "shasum": "" }, "require": { @@ -915,22 +973,22 @@ ], "support": { "issues": "https://github.com/awslabs/aws-crt-php/issues", - "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.6" + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.7" }, - "time": "2024-06-13T17:21:28+00:00" + "time": "2024-10-18T22:15:13+00:00" }, { "name": "aws/aws-sdk-php", - "version": "3.324.0", + "version": "3.327.1", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "b258712f0d986e00e1143d55246b6f9e344c7184" + "reference": "3d52ec587989b136e486f94eff3dd316465aeb42" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/b258712f0d986e00e1143d55246b6f9e344c7184", - "reference": "b258712f0d986e00e1143d55246b6f9e344c7184", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/3d52ec587989b136e486f94eff3dd316465aeb42", + "reference": "3d52ec587989b136e486f94eff3dd316465aeb42", "shasum": "" }, "require": { @@ -1013,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.324.0" + "source": "https://github.com/aws/aws-sdk-php/tree/3.327.1" }, - "time": "2024-10-10T18:06:36+00:00" + "time": "2024-11-15T01:53:30+00:00" }, { "name": "bacon/bacon-qr-code", @@ -1133,26 +1191,26 @@ }, { "name": "carbonphp/carbon-doctrine-types", - "version": "2.1.0", + "version": "3.2.0", "source": { "type": "git", "url": "https://github.com/CarbonPHP/carbon-doctrine-types.git", - "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb" + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", - "reference": "99f76ffa36cce3b70a4a6abce41dba15ca2e84cb", + "url": "https://api.github.com/repos/CarbonPHP/carbon-doctrine-types/zipball/18ba5ddfec8976260ead6e866180bd5d2f71aa1d", + "reference": "18ba5ddfec8976260ead6e866180bd5d2f71aa1d", "shasum": "" }, "require": { - "php": "^7.4 || ^8.0" + "php": "^8.1" }, "conflict": { - "doctrine/dbal": "<3.7.0 || >=4.0.0" + "doctrine/dbal": "<4.0.0 || >=5.0.0" }, "require-dev": { - "doctrine/dbal": "^3.7.0", + "doctrine/dbal": "^4.0.0", "nesbot/carbon": "^2.71.0 || ^3.0.0", "phpunit/phpunit": "^10.3" }, @@ -1182,7 +1240,7 @@ ], "support": { "issues": "https://github.com/CarbonPHP/carbon-doctrine-types/issues", - "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/2.1.0" + "source": "https://github.com/CarbonPHP/carbon-doctrine-types/tree/3.2.0" }, "funding": [ { @@ -1198,7 +1256,7 @@ "type": "tidelift" } ], - "time": "2023-12-11T17:09:12+00:00" + "time": "2024-02-09T16:56:22+00:00" }, { "name": "danharrin/livewire-rate-limiting", @@ -1423,142 +1481,44 @@ }, "time": "2024-07-08T12:26:09+00:00" }, - { - "name": "doctrine/cache", - "version": "2.2.0", - "source": { - "type": "git", - "url": "https://github.com/doctrine/cache.git", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb", - "reference": "1ca8f21980e770095a31456042471a57bc4c68fb", - "shasum": "" - }, - "require": { - "php": "~7.1 || ^8.0" - }, - "conflict": { - "doctrine/common": ">2.2,<2.4" - }, - "require-dev": { - "cache/integration-tests": "dev-master", - "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "psr/cache": "^1.0 || ^2.0 || ^3.0", - "symfony/cache": "^4.4 || ^5.4 || ^6", - "symfony/var-exporter": "^4.4 || ^5.4 || ^6" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - } - ], - "description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.", - "homepage": "https://www.doctrine-project.org/projects/cache.html", - "keywords": [ - "abstraction", - "apcu", - "cache", - "caching", - "couchdb", - "memcached", - "php", - "redis", - "xcache" - ], - "support": { - "issues": "https://github.com/doctrine/cache/issues", - "source": "https://github.com/doctrine/cache/tree/2.2.0" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache", - "type": "tidelift" - } - ], - "time": "2022-05-20T20:07:39+00:00" - }, { "name": "doctrine/dbal", - "version": "3.9.3", + "version": "4.2.1", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "61446f07fcb522414d6cfd8b1c3e5f9e18c579ba" + "reference": "dadd35300837a3a2184bd47d403333b15d0a9bd0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/61446f07fcb522414d6cfd8b1c3e5f9e18c579ba", - "reference": "61446f07fcb522414d6cfd8b1c3e5f9e18c579ba", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/dadd35300837a3a2184bd47d403333b15d0a9bd0", + "reference": "dadd35300837a3a2184bd47d403333b15d0a9bd0", "shasum": "" }, "require": { - "composer-runtime-api": "^2", - "doctrine/cache": "^1.11|^2.0", "doctrine/deprecations": "^0.5.3|^1", - "doctrine/event-manager": "^1|^2", - "php": "^7.4 || ^8.0", + "php": "^8.1", "psr/cache": "^1|^2|^3", "psr/log": "^1|^2|^3" }, "require-dev": { "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", - "jetbrains/phpstorm-stubs": "2023.1", + "jetbrains/phpstorm-stubs": "2023.2", "phpstan/phpstan": "1.12.6", + "phpstan/phpstan-phpunit": "1.4.0", "phpstan/phpstan-strict-rules": "^1.6", - "phpunit/phpunit": "9.6.20", - "psalm/plugin-phpunit": "0.18.4", + "phpunit/phpunit": "10.5.30", + "psalm/plugin-phpunit": "0.19.0", "slevomat/coding-standard": "8.13.1", "squizlabs/php_codesniffer": "3.10.2", - "symfony/cache": "^5.4|^6.0|^7.0", - "symfony/console": "^4.4|^5.4|^6.0|^7.0", - "vimeo/psalm": "4.30.0" + "symfony/cache": "^6.3.8|^7.0", + "symfony/console": "^5.4|^6.3|^7.0", + "vimeo/psalm": "5.25.0" }, "suggest": { "symfony/console": "For helpful console commands such as SQL execution and import of files." }, - "bin": [ - "bin/doctrine-dbal" - ], "type": "library", "autoload": { "psr-4": { @@ -1611,7 +1571,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.9.3" + "source": "https://github.com/doctrine/dbal/tree/4.2.1" }, "funding": [ { @@ -1627,7 +1587,7 @@ "type": "tidelift" } ], - "time": "2024-10-10T17:56:43+00:00" + "time": "2024-10-10T18:01:27+00:00" }, { "name": "doctrine/deprecations", @@ -1676,97 +1636,6 @@ }, "time": "2024-01-30T19:34:25+00:00" }, - { - "name": "doctrine/event-manager", - "version": "2.0.1", - "source": { - "type": "git", - "url": "https://github.com/doctrine/event-manager.git", - "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/doctrine/event-manager/zipball/b680156fa328f1dfd874fd48c7026c41570b9c6e", - "reference": "b680156fa328f1dfd874fd48c7026c41570b9c6e", - "shasum": "" - }, - "require": { - "php": "^8.1" - }, - "conflict": { - "doctrine/common": "<2.9" - }, - "require-dev": { - "doctrine/coding-standard": "^12", - "phpstan/phpstan": "^1.8.8", - "phpunit/phpunit": "^10.5", - "vimeo/psalm": "^5.24" - }, - "type": "library", - "autoload": { - "psr-4": { - "Doctrine\\Common\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Guilherme Blanco", - "email": "guilhermeblanco@gmail.com" - }, - { - "name": "Roman Borschel", - "email": "roman@code-factory.org" - }, - { - "name": "Benjamin Eberlei", - "email": "kontakt@beberlei.de" - }, - { - "name": "Jonathan Wage", - "email": "jonwage@gmail.com" - }, - { - "name": "Johannes Schmitt", - "email": "schmittjoh@gmail.com" - }, - { - "name": "Marco Pivetta", - "email": "ocramius@gmail.com" - } - ], - "description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.", - "homepage": "https://www.doctrine-project.org/projects/event-manager.html", - "keywords": [ - "event", - "event dispatcher", - "event manager", - "event system", - "events" - ], - "support": { - "issues": "https://github.com/doctrine/event-manager/issues", - "source": "https://github.com/doctrine/event-manager/tree/2.0.1" - }, - "funding": [ - { - "url": "https://www.doctrine-project.org/sponsorship.html", - "type": "custom" - }, - { - "url": "https://www.patreon.com/phpdoctrine", - "type": "patreon" - }, - { - "url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager", - "type": "tidelift" - } - ], - "time": "2024-05-22T20:47:39+00:00" - }, { "name": "doctrine/inflector", "version": "2.0.10", @@ -2391,16 +2260,16 @@ }, { "name": "guzzlehttp/promises", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8" + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", - "reference": "6ea8dd08867a2a42619d65c3deb2c0fcbf81c8f8", + "url": "https://api.github.com/repos/guzzle/promises/zipball/f9c436286ab2892c7db7be8c8da4ef61ccf7b455", + "reference": "f9c436286ab2892c7db7be8c8da4ef61ccf7b455", "shasum": "" }, "require": { @@ -2454,7 +2323,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.3" + "source": "https://github.com/guzzle/promises/tree/2.0.4" }, "funding": [ { @@ -2470,7 +2339,7 @@ "type": "tidelift" } ], - "time": "2024-07-18T10:29:17+00:00" + "time": "2024-10-17T10:06:22+00:00" }, { "name": "guzzlehttp/psr7", @@ -2793,16 +2662,16 @@ }, { "name": "laravel/fortify", - "version": "v1.24.2", + "version": "v1.24.5", "source": { "type": "git", "url": "https://github.com/laravel/fortify.git", - "reference": "42695c45087e5abb3e173725b4f1ef4956a7b47d" + "reference": "bba8c2ecc3fcc78e8632e0d719ae10bef6343eef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/fortify/zipball/42695c45087e5abb3e173725b4f1ef4956a7b47d", - "reference": "42695c45087e5abb3e173725b4f1ef4956a7b47d", + "url": "https://api.github.com/repos/laravel/fortify/zipball/bba8c2ecc3fcc78e8632e0d719ae10bef6343eef", + "reference": "bba8c2ecc3fcc78e8632e0d719ae10bef6343eef", "shasum": "" }, "require": { @@ -2854,20 +2723,20 @@ "issues": "https://github.com/laravel/fortify/issues", "source": "https://github.com/laravel/fortify" }, - "time": "2024-09-16T19:20:52+00:00" + "time": "2024-11-12T14:51:12+00:00" }, { "name": "laravel/framework", - "version": "v11.27.2", + "version": "v11.31.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "a51d1f2b771c542324a3d9b76a98b1bbc75c0ee9" + "reference": "365090ed2c68244e3141cdb5e247cdf3dfba2c40" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/a51d1f2b771c542324a3d9b76a98b1bbc75c0ee9", - "reference": "a51d1f2b771c542324a3d9b76a98b1bbc75c0ee9", + "url": "https://api.github.com/repos/laravel/framework/zipball/365090ed2c68244e3141cdb5e247cdf3dfba2c40", + "reference": "365090ed2c68244e3141cdb5e247cdf3dfba2c40", "shasum": "" }, "require": { @@ -3063,20 +2932,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2024-10-09T04:17:35+00:00" + "time": "2024-11-12T15:36:15+00:00" }, { "name": "laravel/horizon", - "version": "v5.29.1", + "version": "v5.29.3", "source": { "type": "git", "url": "https://github.com/laravel/horizon.git", - "reference": "9f482f21c23ed01c2366d1157843165165579c23" + "reference": "a48d242759704e598242074daf0060bbeb6ed46d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/horizon/zipball/9f482f21c23ed01c2366d1157843165165579c23", - "reference": "9f482f21c23ed01c2366d1157843165165579c23", + "url": "https://api.github.com/repos/laravel/horizon/zipball/a48d242759704e598242074daf0060bbeb6ed46d", + "reference": "a48d242759704e598242074daf0060bbeb6ed46d", "shasum": "" }, "require": { @@ -3091,6 +2960,7 @@ "ramsey/uuid": "^4.0", "symfony/console": "^6.0|^7.0", "symfony/error-handler": "^6.0|^7.0", + "symfony/polyfill-php83": "^1.28", "symfony/process": "^6.0|^7.0" }, "require-dev": { @@ -3140,22 +3010,22 @@ ], "support": { "issues": "https://github.com/laravel/horizon/issues", - "source": "https://github.com/laravel/horizon/tree/v5.29.1" + "source": "https://github.com/laravel/horizon/tree/v5.29.3" }, - "time": "2024-10-08T18:23:02+00:00" + "time": "2024-11-07T21:51:45+00:00" }, { "name": "laravel/pail", - "version": "v1.1.5", + "version": "v1.2.1", "source": { "type": "git", "url": "https://github.com/laravel/pail.git", - "reference": "b33ad8321416fe86efed7bf398f3306c47b4871b" + "reference": "353ac12134b98e2e7c3333d916bd3e523931e583" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pail/zipball/b33ad8321416fe86efed7bf398f3306c47b4871b", - "reference": "b33ad8321416fe86efed7bf398f3306c47b4871b", + "url": "https://api.github.com/repos/laravel/pail/zipball/353ac12134b98e2e7c3333d916bd3e523931e583", + "reference": "353ac12134b98e2e7c3333d916bd3e523931e583", "shasum": "" }, "require": { @@ -3170,8 +3040,9 @@ "symfony/console": "^6.0|^7.0" }, "require-dev": { + "laravel/framework": "^10.24|^11.0", "laravel/pint": "^1.13", - "orchestra/testbench": "^8.12|^9.0", + "orchestra/testbench-core": "^8.12|^9.0", "pestphp/pest": "^2.20", "pestphp/pest-plugin-type-coverage": "^2.3", "phpstan/phpstan": "^1.10", @@ -3219,25 +3090,25 @@ "issues": "https://github.com/laravel/pail/issues", "source": "https://github.com/laravel/pail" }, - "time": "2024-10-15T20:06:24+00:00" + "time": "2024-10-23T12:56:23+00:00" }, { "name": "laravel/prompts", - "version": "v0.1.25", + "version": "v0.3.2", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95" + "reference": "0e0535747c6b8d6d10adca8b68293cf4517abb0f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/7b4029a84c37cb2725fc7f011586e2997040bc95", - "reference": "7b4029a84c37cb2725fc7f011586e2997040bc95", + "url": "https://api.github.com/repos/laravel/prompts/zipball/0e0535747c6b8d6d10adca8b68293cf4517abb0f", + "reference": "0e0535747c6b8d6d10adca8b68293cf4517abb0f", "shasum": "" }, "require": { + "composer-runtime-api": "^2.2", "ext-mbstring": "*", - "illuminate/collections": "^10.0|^11.0", "php": "^8.1", "symfony/console": "^6.2|^7.0" }, @@ -3246,8 +3117,9 @@ "laravel/framework": ">=10.17.0 <10.25.0" }, "require-dev": { + "illuminate/collections": "^10.0|^11.0", "mockery/mockery": "^1.5", - "pestphp/pest": "^2.3", + "pestphp/pest": "^2.3|^3.4", "phpstan/phpstan": "^1.11", "phpstan/phpstan-mockery": "^1.1" }, @@ -3257,7 +3129,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "0.1.x-dev" + "dev-main": "0.3.x-dev" } }, "autoload": { @@ -3275,9 +3147,9 @@ "description": "Add beautiful and user-friendly forms to your command-line applications.", "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.25" + "source": "https://github.com/laravel/prompts/tree/v0.3.2" }, - "time": "2024-08-12T22:06:33+00:00" + "time": "2024-11-12T14:59:47+00:00" }, { "name": "laravel/sanctum", @@ -3345,16 +3217,16 @@ }, { "name": "laravel/serializable-closure", - "version": "v1.3.5", + "version": "v1.3.6", "source": { "type": "git", "url": "https://github.com/laravel/serializable-closure.git", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c" + "reference": "f865a58ea3a0107c336b7045104c75243fa59d96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", - "reference": "1dc4a3dbfa2b7628a3114e43e32120cce7cdda9c", + "url": "https://api.github.com/repos/laravel/serializable-closure/zipball/f865a58ea3a0107c336b7045104c75243fa59d96", + "reference": "f865a58ea3a0107c336b7045104c75243fa59d96", "shasum": "" }, "require": { @@ -3402,7 +3274,7 @@ "issues": "https://github.com/laravel/serializable-closure/issues", "source": "https://github.com/laravel/serializable-closure" }, - "time": "2024-09-23T13:33:08+00:00" + "time": "2024-11-11T17:06:04+00:00" }, { "name": "laravel/socialite", @@ -3607,16 +3479,16 @@ }, { "name": "lcobucci/jwt", - "version": "5.4.0", + "version": "5.4.2", "source": { "type": "git", "url": "https://github.com/lcobucci/jwt.git", - "reference": "aac4fd512681fd5cb4b77d2105ab7ec700c72051" + "reference": "ea1ce71cbf9741e445a5914e2f67cdbb484ff712" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/lcobucci/jwt/zipball/aac4fd512681fd5cb4b77d2105ab7ec700c72051", - "reference": "aac4fd512681fd5cb4b77d2105ab7ec700c72051", + "url": "https://api.github.com/repos/lcobucci/jwt/zipball/ea1ce71cbf9741e445a5914e2f67cdbb484ff712", + "reference": "ea1ce71cbf9741e445a5914e2f67cdbb484ff712", "shasum": "" }, "require": { @@ -3664,7 +3536,7 @@ ], "support": { "issues": "https://github.com/lcobucci/jwt/issues", - "source": "https://github.com/lcobucci/jwt/tree/5.4.0" + "source": "https://github.com/lcobucci/jwt/tree/5.4.2" }, "funding": [ { @@ -3676,7 +3548,7 @@ "type": "patreon" } ], - "time": "2024-10-08T22:06:45+00:00" + "time": "2024-11-07T12:54:35+00:00" }, { "name": "league/commonmark", @@ -4410,16 +4282,16 @@ }, { "name": "livewire/livewire", - "version": "v3.4.9", + "version": "v3.5.12", "source": { "type": "git", "url": "https://github.com/livewire/livewire.git", - "reference": "c65b3f0798ab2c9338213ede3588c3cdf4e6fcc0" + "reference": "3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/c65b3f0798ab2c9338213ede3588c3cdf4e6fcc0", - "reference": "c65b3f0798ab2c9338213ede3588c3cdf4e6fcc0", + "url": "https://api.github.com/repos/livewire/livewire/zipball/3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d", + "reference": "3c8d1f9d7d9098aaea663093ae168f2d5d2ae73d", "shasum": "" }, "require": { @@ -4427,17 +4299,18 @@ "illuminate/routing": "^10.0|^11.0", "illuminate/support": "^10.0|^11.0", "illuminate/validation": "^10.0|^11.0", + "laravel/prompts": "^0.1.24|^0.2|^0.3", "league/mime-type-detection": "^1.9", "php": "^8.1", + "symfony/console": "^6.0|^7.0", "symfony/http-kernel": "^6.2|^7.0" }, "require-dev": { "calebporzio/sushi": "^2.1", - "laravel/framework": "^10.0|^11.0", - "laravel/prompts": "^0.1.6", + "laravel/framework": "^10.15.0|^11.0", "mockery/mockery": "^1.3.1", - "orchestra/testbench": "8.20.0|^9.0", - "orchestra/testbench-dusk": "8.20.0|^9.0", + "orchestra/testbench": "^8.21.0|^9.0", + "orchestra/testbench-dusk": "^8.24|^9.1", "phpunit/phpunit": "^10.4", "psy/psysh": "^0.11.22|^0.12" }, @@ -4473,7 +4346,7 @@ "description": "A front-end framework for Laravel.", "support": { "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.4.9" + "source": "https://github.com/livewire/livewire/tree/v3.5.12" }, "funding": [ { @@ -4481,31 +4354,31 @@ "type": "github" } ], - "time": "2024-03-14T14:03:32+00:00" + "time": "2024-10-15T19:35:06+00:00" }, { "name": "log1x/laravel-webfonts", - "version": "v1.0.1", + "version": "v1.0.2", "source": { "type": "git", "url": "https://github.com/Log1x/laravel-webfonts.git", - "reference": "0d38122aa7f5501394006a6715f7d97dac223507" + "reference": "128a20af26f02db84df21abc6524e5a069cf20a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Log1x/laravel-webfonts/zipball/0d38122aa7f5501394006a6715f7d97dac223507", - "reference": "0d38122aa7f5501394006a6715f7d97dac223507", + "url": "https://api.github.com/repos/Log1x/laravel-webfonts/zipball/128a20af26f02db84df21abc6524e5a069cf20a4", + "reference": "128a20af26f02db84df21abc6524e5a069cf20a4", "shasum": "" }, "require": { "guzzlehttp/guzzle": "^7.8", - "laravel/prompts": "^0.1.15", + "laravel/prompts": "^0.1|^0.2|^0.3", "php": ">=8.1" }, "require-dev": { - "illuminate/console": "^10.41", - "illuminate/http": "^10.41", - "illuminate/support": "^10.41", + "illuminate/console": "^10.0|^11.0", + "illuminate/http": "^10.0|^11.0", + "illuminate/support": "^10.0|^11.0", "laravel/pint": "^1.13" }, "type": "package", @@ -4535,7 +4408,7 @@ "description": "Download, install, and preload over 1500 Google fonts locally in your Laravel project", "support": { "issues": "https://github.com/Log1x/laravel-webfonts/issues", - "source": "https://github.com/Log1x/laravel-webfonts/tree/v1.0.1" + "source": "https://github.com/Log1x/laravel-webfonts/tree/v1.0.2" }, "funding": [ { @@ -4543,7 +4416,7 @@ "type": "github" } ], - "time": "2024-03-28T11:53:11+00:00" + "time": "2024-11-12T19:00:31+00:00" }, { "name": "lorisleiva/laravel-actions", @@ -4695,16 +4568,16 @@ }, { "name": "monolog/monolog", - "version": "3.7.0", + "version": "3.8.0", "source": { "type": "git", "url": "https://github.com/Seldaek/monolog.git", - "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8" + "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/f4393b648b78a5408747de94fca38beb5f7e9ef8", - "reference": "f4393b648b78a5408747de94fca38beb5f7e9ef8", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/32e515fdc02cdafbe4593e30a9350d486b125b67", + "reference": "32e515fdc02cdafbe4593e30a9350d486b125b67", "shasum": "" }, "require": { @@ -4724,12 +4597,14 @@ "guzzlehttp/psr7": "^2.2", "mongodb/mongodb": "^1.8", "php-amqplib/php-amqplib": "~2.4 || ^3", - "phpstan/phpstan": "^1.9", - "phpstan/phpstan-deprecation-rules": "^1.0", - "phpstan/phpstan-strict-rules": "^1.4", - "phpunit/phpunit": "^10.5.17", + "php-console/php-console": "^3.1.8", + "phpstan/phpstan": "^2", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-strict-rules": "^2", + "phpunit/phpunit": "^10.5.17 || ^11.0.7", "predis/predis": "^1.1 || ^2", - "ruflin/elastica": "^7", + "rollbar/rollbar": "^4.0", + "ruflin/elastica": "^7 || ^8", "symfony/mailer": "^5.4 || ^6", "symfony/mime": "^5.4 || ^6" }, @@ -4780,7 +4655,7 @@ ], "support": { "issues": "https://github.com/Seldaek/monolog/issues", - "source": "https://github.com/Seldaek/monolog/tree/3.7.0" + "source": "https://github.com/Seldaek/monolog/tree/3.8.0" }, "funding": [ { @@ -4792,7 +4667,7 @@ "type": "tidelift" } ], - "time": "2024-06-28T09:40:51+00:00" + "time": "2024-11-12T13:57:08+00:00" }, { "name": "mtdowling/jmespath.php", @@ -4862,20 +4737,20 @@ }, { "name": "nesbot/carbon", - "version": "3.8.0", + "version": "3.8.2", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f" + "reference": "e1268cdbc486d97ce23fef2c666dc3c6b6de9947" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/bbd3eef89af8ba66a3aa7952b5439168fbcc529f", - "reference": "bbd3eef89af8ba66a3aa7952b5439168fbcc529f", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/e1268cdbc486d97ce23fef2c666dc3c6b6de9947", + "reference": "e1268cdbc486d97ce23fef2c666dc3c6b6de9947", "shasum": "" }, "require": { - "carbonphp/carbon-doctrine-types": "*", + "carbonphp/carbon-doctrine-types": "<100.0", "ext-json": "*", "php": "^8.1", "psr/clock": "^1.0", @@ -4964,7 +4839,7 @@ "type": "tidelift" } ], - "time": "2024-08-19T06:22:39+00:00" + "time": "2024-11-07T17:46:48+00:00" }, { "name": "nette/schema", @@ -5227,32 +5102,31 @@ }, { "name": "nunomaduro/termwind", - "version": "v2.1.0", + "version": "v2.2.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/termwind.git", - "reference": "e5f21eade88689536c0cdad4c3cd75f3ed26e01a" + "reference": "42c84e4e8090766bbd6445d06cd6e57650626ea3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/e5f21eade88689536c0cdad4c3cd75f3ed26e01a", - "reference": "e5f21eade88689536c0cdad4c3cd75f3ed26e01a", + "url": "https://api.github.com/repos/nunomaduro/termwind/zipball/42c84e4e8090766bbd6445d06cd6e57650626ea3", + "reference": "42c84e4e8090766bbd6445d06cd6e57650626ea3", "shasum": "" }, "require": { "ext-mbstring": "*", "php": "^8.2", - "symfony/console": "^7.0.4" + "symfony/console": "^7.1.5" }, "require-dev": { - "ergebnis/phpstan-rules": "^2.2.0", - "illuminate/console": "^11.1.1", - "laravel/pint": "^1.15.0", - "mockery/mockery": "^1.6.11", - "pestphp/pest": "^2.34.6", - "phpstan/phpstan": "^1.10.66", - "phpstan/phpstan-strict-rules": "^1.5.2", - "symfony/var-dumper": "^7.0.4", + "illuminate/console": "^11.28.0", + "laravel/pint": "^1.18.1", + "mockery/mockery": "^1.6.12", + "pestphp/pest": "^2.36.0", + "phpstan/phpstan": "^1.12.6", + "phpstan/phpstan-strict-rules": "^1.6.1", + "symfony/var-dumper": "^7.1.5", "thecodingmachine/phpstan-strict-rules": "^1.0.0" }, "type": "library", @@ -5295,7 +5169,7 @@ ], "support": { "issues": "https://github.com/nunomaduro/termwind/issues", - "source": "https://github.com/nunomaduro/termwind/tree/v2.1.0" + "source": "https://github.com/nunomaduro/termwind/tree/v2.2.0" }, "funding": [ { @@ -5311,7 +5185,7 @@ "type": "github" } ], - "time": "2024-09-05T15:25:50+00:00" + "time": "2024-10-15T16:15:16+00:00" }, { "name": "nyholm/psr7", @@ -5510,30 +5384,35 @@ }, { "name": "paragonie/sodium_compat", - "version": "v1.21.1", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/paragonie/sodium_compat.git", - "reference": "bb312875dcdd20680419564fe42ba1d9564b9e37" + "reference": "a673d5f310477027cead2e2f2b6db5d8368157cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/bb312875dcdd20680419564fe42ba1d9564b9e37", - "reference": "bb312875dcdd20680419564fe42ba1d9564b9e37", + "url": "https://api.github.com/repos/paragonie/sodium_compat/zipball/a673d5f310477027cead2e2f2b6db5d8368157cb", + "reference": "a673d5f310477027cead2e2f2b6db5d8368157cb", "shasum": "" }, "require": { - "paragonie/random_compat": ">=1", - "php": "^5.2.4|^5.3|^5.4|^5.5|^5.6|^7|^8" + "php": "^8.1", + "php-64bit": "*" }, "require-dev": { - "phpunit/phpunit": "^3|^4|^5|^6|^7|^8|^9" + "phpunit/phpunit": "^7|^8|^9", + "vimeo/psalm": "^4|^5" }, "suggest": { - "ext-libsodium": "PHP < 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security.", - "ext-sodium": "PHP >= 7.0: Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." + "ext-sodium": "Better performance, password hashing (Argon2i), secure memory management (memzero), and better security." }, "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, "autoload": { "files": [ "autoload.php" @@ -5590,9 +5469,9 @@ ], "support": { "issues": "https://github.com/paragonie/sodium_compat/issues", - "source": "https://github.com/paragonie/sodium_compat/tree/v1.21.1" + "source": "https://github.com/paragonie/sodium_compat/tree/v2.1.0" }, - "time": "2024-04-22T22:05:04+00:00" + "time": "2024-09-04T12:51:01+00:00" }, { "name": "php-di/invoker", @@ -5722,6 +5601,73 @@ ], "time": "2024-07-21T15:55:45+00:00" }, + { + "name": "phpdocumentor/reflection", + "version": "6.0.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/Reflection.git", + "reference": "61e2f1fe7683e9647b9ed8d9e53d08699385267d" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/Reflection/zipball/61e2f1fe7683e9647b9ed8d9e53d08699385267d", + "reference": "61e2f1fe7683e9647b9ed8d9e53d08699385267d", + "shasum": "" + }, + "require": { + "nikic/php-parser": "~4.18 || ^5.0", + "php": "8.1.*|8.2.*|8.3.*", + "phpdocumentor/reflection-common": "^2.1", + "phpdocumentor/reflection-docblock": "^5", + "phpdocumentor/type-resolver": "^1.2", + "symfony/polyfill-php80": "^1.28", + "webmozart/assert": "^1.7" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/coding-standard": "^12.0", + "mikey179/vfsstream": "~1.2", + "mockery/mockery": "~1.6.0", + "phpspec/prophecy-phpunit": "^2.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^10.0", + "psalm/phar": "^5.24", + "rector/rector": "^1.0.0", + "squizlabs/php_codesniffer": "^3.8" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-5.x": "5.3.x-dev", + "dev-6.x": "6.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\": "src/phpDocumentor" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Reflection library to do Static Analysis for PHP Projects", + "homepage": "http://www.phpdoc.org", + "keywords": [ + "phpDocumentor", + "phpdoc", + "reflection", + "static analysis" + ], + "support": { + "issues": "https://github.com/phpDocumentor/Reflection/issues", + "source": "https://github.com/phpDocumentor/Reflection/tree/6.0.0" + }, + "time": "2024-05-23T19:28:12+00:00" + }, { "name": "phpdocumentor/reflection-common", "version": "2.2.0", @@ -5776,24 +5722,88 @@ "time": "2020-06-27T09:03:43+00:00" }, { - "name": "phpdocumentor/type-resolver", - "version": "1.8.2", + "name": "phpdocumentor/reflection-docblock", + "version": "5.6.0", "source": { "type": "git", - "url": "https://github.com/phpDocumentor/TypeResolver.git", - "reference": "153ae662783729388a584b4361f2545e4d841e3c" + "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", + "reference": "f3558a4c23426d12bffeaab463f8a8d8b681193c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/153ae662783729388a584b4361f2545e4d841e3c", - "reference": "153ae662783729388a584b4361f2545e4d841e3c", + "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/f3558a4c23426d12bffeaab463f8a8d8b681193c", + "reference": "f3558a4c23426d12bffeaab463f8a8d8b681193c", + "shasum": "" + }, + "require": { + "doctrine/deprecations": "^1.1", + "ext-filter": "*", + "php": "^7.4 || ^8.0", + "phpdocumentor/reflection-common": "^2.2", + "phpdocumentor/type-resolver": "^1.7", + "phpstan/phpdoc-parser": "^1.7|^2.0", + "webmozart/assert": "^1.9.1" + }, + "require-dev": { + "mockery/mockery": "~1.3.5 || ~1.6.0", + "phpstan/extension-installer": "^1.1", + "phpstan/phpstan": "^1.8", + "phpstan/phpstan-mockery": "^1.1", + "phpstan/phpstan-webmozart-assert": "^1.2", + "phpunit/phpunit": "^9.5", + "psalm/phar": "^5.26" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "5.x-dev" + } + }, + "autoload": { + "psr-4": { + "phpDocumentor\\Reflection\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Mike van Riel", + "email": "me@mikevanriel.com" + }, + { + "name": "Jaap van Otterdijk", + "email": "opensource@ijaap.nl" + } + ], + "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" + }, + "time": "2024-11-12T11:25:25+00:00" + }, + { + "name": "phpdocumentor/type-resolver", + "version": "1.10.0", + "source": { + "type": "git", + "url": "https://github.com/phpDocumentor/TypeResolver.git", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/phpDocumentor/TypeResolver/zipball/679e3ce485b99e84c775d28e2e96fade9a7fb50a", + "reference": "679e3ce485b99e84c775d28e2e96fade9a7fb50a", "shasum": "" }, "require": { "doctrine/deprecations": "^1.0", "php": "^7.3 || ^8.0", "phpdocumentor/reflection-common": "^2.0", - "phpstan/phpdoc-parser": "^1.13" + "phpstan/phpdoc-parser": "^1.18|^2.0" }, "require-dev": { "ext-tokenizer": "*", @@ -5829,9 +5839,9 @@ "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", "support": { "issues": "https://github.com/phpDocumentor/TypeResolver/issues", - "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.8.2" + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.10.0" }, - "time": "2024-02-23T11:10:43+00:00" + "time": "2024-11-09T15:12:26+00:00" }, { "name": "phpoption/phpoption", @@ -6020,30 +6030,30 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.32.0", + "version": "2.0.0", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4" + "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/6ca22b154efdd9e3c68c56f5d94670920a1c19a4", - "reference": "6ca22b154efdd9e3c68c56f5d94670920a1c19a4", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/c00d78fb6b29658347f9d37ebe104bffadf36299", + "reference": "c00d78fb6b29658347f9d37ebe104bffadf36299", "shasum": "" }, "require": { - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "doctrine/annotations": "^2.0", - "nikic/php-parser": "^4.15", + "nikic/php-parser": "^5.3.0", "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^1.5", - "phpstan/phpstan-phpunit": "^1.1", - "phpstan/phpstan-strict-rules": "^1.0", - "phpunit/phpunit": "^9.5", + "phpstan/phpstan": "^2.0", + "phpstan/phpstan-phpunit": "^2.0", + "phpstan/phpstan-strict-rules": "^2.0", + "phpunit/phpunit": "^9.6", "symfony/process": "^5.2" }, "type": "library", @@ -6061,22 +6071,22 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.32.0" + "source": "https://github.com/phpstan/phpdoc-parser/tree/2.0.0" }, - "time": "2024-09-26T07:23:32+00:00" + "time": "2024-10-13T11:29:49+00:00" }, { "name": "phpstan/phpstan", - "version": "1.12.6", + "version": "1.12.10", "source": { "type": "git", "url": "https://github.com/phpstan/phpstan.git", - "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae" + "reference": "fc463b5d0fe906dcf19689be692c65c50406a071" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpstan/zipball/dc4d2f145a88ea7141ae698effd64d9df46527ae", - "reference": "dc4d2f145a88ea7141ae698effd64d9df46527ae", + "url": "https://api.github.com/repos/phpstan/phpstan/zipball/fc463b5d0fe906dcf19689be692c65c50406a071", + "reference": "fc463b5d0fe906dcf19689be692c65c50406a071", "shasum": "" }, "require": { @@ -6121,7 +6131,7 @@ "type": "github" } ], - "time": "2024-10-06T15:03:59+00:00" + "time": "2024-11-11T15:37:09+00:00" }, { "name": "pion/laravel-chunk-upload", @@ -6875,23 +6885,23 @@ }, { "name": "pusher/pusher-php-server", - "version": "7.2.4", + "version": "7.2.6", "source": { "type": "git", "url": "https://github.com/pusher/pusher-http-php.git", - "reference": "de2f72296808f9cafa6a4462b15a768ff130cddb" + "reference": "d89e9997191d18fb0fe03a956fa3ccfe0af524ea" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/de2f72296808f9cafa6a4462b15a768ff130cddb", - "reference": "de2f72296808f9cafa6a4462b15a768ff130cddb", + "url": "https://api.github.com/repos/pusher/pusher-http-php/zipball/d89e9997191d18fb0fe03a956fa3ccfe0af524ea", + "reference": "d89e9997191d18fb0fe03a956fa3ccfe0af524ea", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", "guzzlehttp/guzzle": "^7.2", - "paragonie/sodium_compat": "^1.6", + "paragonie/sodium_compat": "^1.6|^2.0", "php": "^7.3|^8.0", "psr/log": "^1.0|^2.0|^3.0" }, @@ -6930,9 +6940,9 @@ ], "support": { "issues": "https://github.com/pusher/pusher-http-php/issues", - "source": "https://github.com/pusher/pusher-http-php/tree/7.2.4" + "source": "https://github.com/pusher/pusher-http-php/tree/7.2.6" }, - "time": "2023-12-15T10:58:53+00:00" + "time": "2024-10-18T12:04:31+00:00" }, { "name": "ralouphie/getallheaders", @@ -7161,16 +7171,16 @@ }, { "name": "rector/rector", - "version": "1.2.6", + "version": "1.2.10", "source": { "type": "git", "url": "https://github.com/rectorphp/rector.git", - "reference": "6ca85da28159dbd3bb36211c5104b7bc91278e99" + "reference": "40f9cf38c05296bd32f444121336a521a293fa61" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/rectorphp/rector/zipball/6ca85da28159dbd3bb36211c5104b7bc91278e99", - "reference": "6ca85da28159dbd3bb36211c5104b7bc91278e99", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/40f9cf38c05296bd32f444121336a521a293fa61", + "reference": "40f9cf38c05296bd32f444121336a521a293fa61", "shasum": "" }, "require": { @@ -7208,7 +7218,7 @@ ], "support": { "issues": "https://github.com/rectorphp/rector/issues", - "source": "https://github.com/rectorphp/rector/tree/1.2.6" + "source": "https://github.com/rectorphp/rector/tree/1.2.10" }, "funding": [ { @@ -7216,27 +7226,27 @@ "type": "github" } ], - "time": "2024-10-03T08:56:44+00:00" + "time": "2024-11-08T13:59:10+00:00" }, { "name": "resend/resend-laravel", - "version": "v0.13.0", + "version": "v0.15.0", "source": { "type": "git", "url": "https://github.com/resend/resend-laravel.git", - "reference": "23aed22df0d0b23c2952da2aaed6a8b88d301a8a" + "reference": "af914817abc6abaa4522b5cfb177f3519493fd6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/resend/resend-laravel/zipball/23aed22df0d0b23c2952da2aaed6a8b88d301a8a", - "reference": "23aed22df0d0b23c2952da2aaed6a8b88d301a8a", + "url": "https://api.github.com/repos/resend/resend-laravel/zipball/af914817abc6abaa4522b5cfb177f3519493fd6e", + "reference": "af914817abc6abaa4522b5cfb177f3519493fd6e", "shasum": "" }, "require": { "illuminate/http": "^10.0|^11.0", "illuminate/support": "^10.0|^11.0", "php": "^8.1", - "resend/resend-php": "^0.12.0", + "resend/resend-php": "^0.14.0", "symfony/mailer": "^6.2|^7.0" }, "require-dev": { @@ -7283,22 +7293,22 @@ ], "support": { "issues": "https://github.com/resend/resend-laravel/issues", - "source": "https://github.com/resend/resend-laravel/tree/v0.13.0" + "source": "https://github.com/resend/resend-laravel/tree/v0.15.0" }, - "time": "2024-07-08T18:51:42+00:00" + "time": "2024-11-04T18:34:08+00:00" }, { "name": "resend/resend-php", - "version": "v0.12.0", + "version": "v0.14.0", "source": { "type": "git", "url": "https://github.com/resend/resend-php.git", - "reference": "37fb79bb8160ce2de521bf37484ba59e89236521" + "reference": "d7900752bb9839421d40d9e66362bffb3ec07aac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/resend/resend-php/zipball/37fb79bb8160ce2de521bf37484ba59e89236521", - "reference": "37fb79bb8160ce2de521bf37484ba59e89236521", + "url": "https://api.github.com/repos/resend/resend-php/zipball/d7900752bb9839421d40d9e66362bffb3ec07aac", + "reference": "d7900752bb9839421d40d9e66362bffb3ec07aac", "shasum": "" }, "require": { @@ -7340,9 +7350,9 @@ ], "support": { "issues": "https://github.com/resend/resend-php/issues", - "source": "https://github.com/resend/resend-php/tree/v0.12.0" + "source": "https://github.com/resend/resend-php/tree/v0.14.0" }, - "time": "2024-03-04T03:16:28+00:00" + "time": "2024-11-01T02:00:44+00:00" }, { "name": "revolt/event-loop", @@ -7418,16 +7428,16 @@ }, { "name": "sentry/sentry", - "version": "4.9.0", + "version": "4.10.0", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-php.git", - "reference": "788ec170f51ebb22f2809a1e3f78b19ccd39b70d" + "reference": "2af937d47d8aadb8dab0b1d7b9557e495dd12856" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/788ec170f51ebb22f2809a1e3f78b19ccd39b70d", - "reference": "788ec170f51ebb22f2809a1e3f78b19ccd39b70d", + "url": "https://api.github.com/repos/getsentry/sentry-php/zipball/2af937d47d8aadb8dab0b1d7b9557e495dd12856", + "reference": "2af937d47d8aadb8dab0b1d7b9557e495dd12856", "shasum": "" }, "require": { @@ -7445,12 +7455,12 @@ }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.4", - "guzzlehttp/promises": "^1.0|^2.0", + "guzzlehttp/promises": "^2.0.3", "guzzlehttp/psr7": "^1.8.4|^2.1.1", "monolog/monolog": "^1.6|^2.0|^3.0", "phpbench/phpbench": "^1.0", "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^8.5.14|^9.4", + "phpunit/phpunit": "^8.5|^9.6", "symfony/phpunit-bridge": "^5.2|^6.0|^7.0", "vimeo/psalm": "^4.17" }, @@ -7491,7 +7501,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-php/issues", - "source": "https://github.com/getsentry/sentry-php/tree/4.9.0" + "source": "https://github.com/getsentry/sentry-php/tree/4.10.0" }, "funding": [ { @@ -7503,27 +7513,27 @@ "type": "custom" } ], - "time": "2024-08-08T14:40:50+00:00" + "time": "2024-11-06T07:44:19+00:00" }, { "name": "sentry/sentry-laravel", - "version": "4.9.0", + "version": "4.10.0", "source": { "type": "git", "url": "https://github.com/getsentry/sentry-laravel.git", - "reference": "73078e1f26d57f7a10e3bee2a2f543a02f6493c3" + "reference": "cbdd224cc5a224528bf6b19507ad76187b3bccfa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/73078e1f26d57f7a10e3bee2a2f543a02f6493c3", - "reference": "73078e1f26d57f7a10e3bee2a2f543a02f6493c3", + "url": "https://api.github.com/repos/getsentry/sentry-laravel/zipball/cbdd224cc5a224528bf6b19507ad76187b3bccfa", + "reference": "cbdd224cc5a224528bf6b19507ad76187b3bccfa", "shasum": "" }, "require": { "illuminate/support": "^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0", "nyholm/psr7": "^1.0", "php": "^7.2 | ^8.0", - "sentry/sentry": "^4.9", + "sentry/sentry": "^4.10", "symfony/psr-http-message-bridge": "^1.0 | ^2.0 | ^6.0 | ^7.0" }, "require-dev": { @@ -7580,7 +7590,7 @@ ], "support": { "issues": "https://github.com/getsentry/sentry-laravel/issues", - "source": "https://github.com/getsentry/sentry-laravel/tree/4.9.0" + "source": "https://github.com/getsentry/sentry-laravel/tree/4.10.0" }, "funding": [ { @@ -7592,20 +7602,20 @@ "type": "custom" } ], - "time": "2024-09-19T12:58:53+00:00" + "time": "2024-11-07T08:05:24+00:00" }, { "name": "socialiteproviders/manager", - "version": "v4.6.0", + "version": "v4.7.0", "source": { "type": "git", "url": "https://github.com/SocialiteProviders/Manager.git", - "reference": "dea5190981c31b89e52259da9ab1ca4e2b258b21" + "reference": "ab0691b82cec77efd90154c78f1854903455c82f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/dea5190981c31b89e52259da9ab1ca4e2b258b21", - "reference": "dea5190981c31b89e52259da9ab1ca4e2b258b21", + "url": "https://api.github.com/repos/SocialiteProviders/Manager/zipball/ab0691b82cec77efd90154c78f1854903455c82f", + "reference": "ab0691b82cec77efd90154c78f1854903455c82f", "shasum": "" }, "require": { @@ -7666,7 +7676,7 @@ "issues": "https://github.com/socialiteproviders/manager/issues", "source": "https://github.com/socialiteproviders/manager" }, - "time": "2024-05-04T07:57:39+00:00" + "time": "2024-11-10T01:56:18+00:00" }, { "name": "socialiteproviders/microsoft-azure", @@ -7784,16 +7794,16 @@ }, { "name": "spatie/laravel-activitylog", - "version": "4.8.0", + "version": "4.9.0", "source": { "type": "git", "url": "https://github.com/spatie/laravel-activitylog.git", - "reference": "eb6f37dd40af950ce10cf5280f0acfa3e08c3bff" + "reference": "e0fc28178515a5396f48e107ed697719189bbe02" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/eb6f37dd40af950ce10cf5280f0acfa3e08c3bff", - "reference": "eb6f37dd40af950ce10cf5280f0acfa3e08c3bff", + "url": "https://api.github.com/repos/spatie/laravel-activitylog/zipball/e0fc28178515a5396f48e107ed697719189bbe02", + "reference": "e0fc28178515a5396f48e107ed697719189bbe02", "shasum": "" }, "require": { @@ -7859,7 +7869,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-activitylog/issues", - "source": "https://github.com/spatie/laravel-activitylog/tree/4.8.0" + "source": "https://github.com/spatie/laravel-activitylog/tree/4.9.0" }, "funding": [ { @@ -7871,47 +7881,47 @@ "type": "github" } ], - "time": "2024-03-08T22:28:17+00:00" + "time": "2024-10-18T13:38:47+00:00" }, { "name": "spatie/laravel-data", - "version": "3.12.0", + "version": "4.11.1", "source": { "type": "git", "url": "https://github.com/spatie/laravel-data.git", - "reference": "d44e04839407bc32b029be59ba80090a5f720e91" + "reference": "df5b58baebae34475ca35338b4e9a131c9e2a8e0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-data/zipball/d44e04839407bc32b029be59ba80090a5f720e91", - "reference": "d44e04839407bc32b029be59ba80090a5f720e91", + "url": "https://api.github.com/repos/spatie/laravel-data/zipball/df5b58baebae34475ca35338b4e9a131c9e2a8e0", + "reference": "df5b58baebae34475ca35338b4e9a131c9e2a8e0", "shasum": "" }, "require": { - "illuminate/contracts": "^9.30|^10.0|^11.0", + "illuminate/contracts": "^10.0|^11.0", "php": "^8.1", - "phpdocumentor/type-resolver": "^1.5", + "phpdocumentor/reflection": "^6.0", "spatie/laravel-package-tools": "^1.9.0", "spatie/php-structure-discoverer": "^2.0" }, "require-dev": { "fakerphp/faker": "^1.14", "friendsofphp/php-cs-fixer": "^3.0", - "inertiajs/inertia-laravel": "^0.6.3", + "inertiajs/inertia-laravel": "^1.2", + "livewire/livewire": "^3.0", "mockery/mockery": "^1.6", "nesbot/carbon": "^2.63", - "nette/php-generator": "^3.5", "nunomaduro/larastan": "^2.0", - "orchestra/testbench": "^7.6|^8.0", - "pestphp/pest": "^1.22", - "pestphp/pest-plugin-laravel": "^1.3", + "orchestra/testbench": "^8.0|^9.0", + "pestphp/pest": "^2.31", + "pestphp/pest-plugin-laravel": "^2.0", + "pestphp/pest-plugin-livewire": "^2.1", "phpbench/phpbench": "^1.2", "phpstan/extension-installer": "^1.1", - "phpunit/phpunit": "^9.3", + "phpunit/phpunit": "^10.0", "spatie/invade": "^1.0", - "spatie/laravel-typescript-transformer": "^2.1.6", - "spatie/pest-plugin-snapshots": "^1.1", - "spatie/phpunit-snapshot-assertions": "^4.2", + "spatie/laravel-typescript-transformer": "^2.5", + "spatie/pest-plugin-snapshots": "^2.1", "spatie/test-time": "^1.2" }, "type": "library", @@ -7924,8 +7934,7 @@ }, "autoload": { "psr-4": { - "Spatie\\LaravelData\\": "src", - "Spatie\\LaravelData\\Database\\Factories\\": "database/factories" + "Spatie\\LaravelData\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -7948,7 +7957,7 @@ ], "support": { "issues": "https://github.com/spatie/laravel-data/issues", - "source": "https://github.com/spatie/laravel-data/tree/3.12.0" + "source": "https://github.com/spatie/laravel-data/tree/4.11.1" }, "funding": [ { @@ -7956,7 +7965,7 @@ "type": "github" } ], - "time": "2024-04-24T09:27:45+00:00" + "time": "2024-10-23T07:14:53+00:00" }, { "name": "spatie/laravel-package-tools", @@ -8460,16 +8469,16 @@ }, { "name": "stripe/stripe-php", - "version": "v12.8.0", + "version": "v16.2.0", "source": { "type": "git", "url": "https://github.com/stripe/stripe-php.git", - "reference": "6b6f4a775ad46fee4b1df2df4fdfa574365b1621" + "reference": "813ae4961755af28a13bda451689f7a6ed6498cb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/stripe/stripe-php/zipball/6b6f4a775ad46fee4b1df2df4fdfa574365b1621", - "reference": "6b6f4a775ad46fee4b1df2df4fdfa574365b1621", + "url": "https://api.github.com/repos/stripe/stripe-php/zipball/813ae4961755af28a13bda451689f7a6ed6498cb", + "reference": "813ae4961755af28a13bda451689f7a6ed6498cb", "shasum": "" }, "require": { @@ -8513,22 +8522,22 @@ ], "support": { "issues": "https://github.com/stripe/stripe-php/issues", - "source": "https://github.com/stripe/stripe-php/tree/v12.8.0" + "source": "https://github.com/stripe/stripe-php/tree/v16.2.0" }, - "time": "2023-10-16T18:04:12+00:00" + "time": "2024-10-29T21:15:53+00:00" }, { "name": "symfony/clock", - "version": "v7.1.1", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/clock.git", - "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7" + "reference": "97bebc53548684c17ed696bc8af016880f0f098d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/clock/zipball/3dfc8b084853586de51dd1441c6242c76a28cbe7", - "reference": "3dfc8b084853586de51dd1441c6242c76a28cbe7", + "url": "https://api.github.com/repos/symfony/clock/zipball/97bebc53548684c17ed696bc8af016880f0f098d", + "reference": "97bebc53548684c17ed696bc8af016880f0f098d", "shasum": "" }, "require": { @@ -8573,7 +8582,7 @@ "time" ], "support": { - "source": "https://github.com/symfony/clock/tree/v7.1.1" + "source": "https://github.com/symfony/clock/tree/v7.1.6" }, "funding": [ { @@ -8589,20 +8598,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/console", - "version": "v7.1.5", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee" + "reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0fa539d12b3ccf068a722bbbffa07ca7079af9ee", - "reference": "0fa539d12b3ccf068a722bbbffa07ca7079af9ee", + "url": "https://api.github.com/repos/symfony/console/zipball/ff04e5b5ba043d2badfb308197b9e6b42883fcd5", + "reference": "ff04e5b5ba043d2badfb308197b9e6b42883fcd5", "shasum": "" }, "require": { @@ -8666,7 +8675,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.1.5" + "source": "https://github.com/symfony/console/tree/v7.1.8" }, "funding": [ { @@ -8682,20 +8691,20 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:28:38+00:00" + "time": "2024-11-06T14:23:19+00:00" }, { "name": "symfony/css-selector", - "version": "v7.1.1", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4" + "reference": "4aa4f6b3d6749c14d3aa815eef8226632e7bbc66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/1c7cee86c6f812896af54434f8ce29c8d94f9ff4", - "reference": "1c7cee86c6f812896af54434f8ce29c8d94f9ff4", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/4aa4f6b3d6749c14d3aa815eef8226632e7bbc66", + "reference": "4aa4f6b3d6749c14d3aa815eef8226632e7bbc66", "shasum": "" }, "require": { @@ -8731,7 +8740,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v7.1.1" + "source": "https://github.com/symfony/css-selector/tree/v7.1.6" }, "funding": [ { @@ -8747,7 +8756,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/deprecation-contracts", @@ -8818,16 +8827,16 @@ }, { "name": "symfony/error-handler", - "version": "v7.1.3", + "version": "v7.1.7", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "432bb369952795c61ca1def65e078c4a80dad13c" + "reference": "010e44661f4c6babaf8c4862fe68c24a53903342" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/432bb369952795c61ca1def65e078c4a80dad13c", - "reference": "432bb369952795c61ca1def65e078c4a80dad13c", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/010e44661f4c6babaf8c4862fe68c24a53903342", + "reference": "010e44661f4c6babaf8c4862fe68c24a53903342", "shasum": "" }, "require": { @@ -8873,7 +8882,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.3" + "source": "https://github.com/symfony/error-handler/tree/v7.1.7" }, "funding": [ { @@ -8889,20 +8898,20 @@ "type": "tidelift" } ], - "time": "2024-07-26T13:02:51+00:00" + "time": "2024-11-05T15:34:55+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.1.1", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7" + "reference": "87254c78dd50721cfd015b62277a8281c5589702" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", - "reference": "9fa7f7a21beb22a39a8f3f28618b29e50d7a55a7", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/87254c78dd50721cfd015b62277a8281c5589702", + "reference": "87254c78dd50721cfd015b62277a8281c5589702", "shasum": "" }, "require": { @@ -8953,7 +8962,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.1" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.1.6" }, "funding": [ { @@ -8969,7 +8978,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -9049,16 +9058,16 @@ }, { "name": "symfony/finder", - "version": "v7.1.4", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "d95bbf319f7d052082fb7af147e0f835a695e823" + "reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/d95bbf319f7d052082fb7af147e0f835a695e823", - "reference": "d95bbf319f7d052082fb7af147e0f835a695e823", + "url": "https://api.github.com/repos/symfony/finder/zipball/2cb89664897be33f78c65d3d2845954c8d7a43b8", + "reference": "2cb89664897be33f78c65d3d2845954c8d7a43b8", "shasum": "" }, "require": { @@ -9093,7 +9102,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.4" + "source": "https://github.com/symfony/finder/tree/v7.1.6" }, "funding": [ { @@ -9109,20 +9118,20 @@ "type": "tidelift" } ], - "time": "2024-08-13T14:28:19+00:00" + "time": "2024-10-01T08:31:23+00:00" }, { "name": "symfony/http-foundation", - "version": "v7.1.5", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "e30ef73b1e44eea7eb37ba69600a354e553f694b" + "reference": "f4419ec69ccfc3f725a4de7c20e4e57626d10112" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/e30ef73b1e44eea7eb37ba69600a354e553f694b", - "reference": "e30ef73b1e44eea7eb37ba69600a354e553f694b", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f4419ec69ccfc3f725a4de7c20e4e57626d10112", + "reference": "f4419ec69ccfc3f725a4de7c20e4e57626d10112", "shasum": "" }, "require": { @@ -9132,12 +9141,12 @@ }, "conflict": { "doctrine/dbal": "<3.6", - "symfony/cache": "<6.4" + "symfony/cache": "<6.4.12|>=7.0,<7.1.5" }, "require-dev": { "doctrine/dbal": "^3.6|^4", "predis/predis": "^1.1|^2.0", - "symfony/cache": "^6.4|^7.0", + "symfony/cache": "^6.4.12|^7.1.5", "symfony/dependency-injection": "^6.4|^7.0", "symfony/expression-language": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", @@ -9170,7 +9179,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.5" + "source": "https://github.com/symfony/http-foundation/tree/v7.1.8" }, "funding": [ { @@ -9186,20 +9195,20 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:28:38+00:00" + "time": "2024-11-09T09:16:45+00:00" }, { "name": "symfony/http-kernel", - "version": "v7.1.5", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "44204d96150a9df1fc57601ec933d23fefc2d65b" + "reference": "33fef24e3dc79d6d30bf4936531f2f4bd2ca189e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/44204d96150a9df1fc57601ec933d23fefc2d65b", - "reference": "44204d96150a9df1fc57601ec933d23fefc2d65b", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/33fef24e3dc79d6d30bf4936531f2f4bd2ca189e", + "reference": "33fef24e3dc79d6d30bf4936531f2f4bd2ca189e", "shasum": "" }, "require": { @@ -9284,7 +9293,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.5" + "source": "https://github.com/symfony/http-kernel/tree/v7.1.8" }, "funding": [ { @@ -9300,20 +9309,20 @@ "type": "tidelift" } ], - "time": "2024-09-21T06:09:21+00:00" + "time": "2024-11-13T14:25:32+00:00" }, { "name": "symfony/mailer", - "version": "v7.1.5", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "bbf21460c56f29810da3df3e206e38dfbb01e80b" + "reference": "69c9948451fb3a6a4d47dc8261d1794734e76cdd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/bbf21460c56f29810da3df3e206e38dfbb01e80b", - "reference": "bbf21460c56f29810da3df3e206e38dfbb01e80b", + "url": "https://api.github.com/repos/symfony/mailer/zipball/69c9948451fb3a6a4d47dc8261d1794734e76cdd", + "reference": "69c9948451fb3a6a4d47dc8261d1794734e76cdd", "shasum": "" }, "require": { @@ -9364,7 +9373,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v7.1.5" + "source": "https://github.com/symfony/mailer/tree/v7.1.6" }, "funding": [ { @@ -9380,20 +9389,20 @@ "type": "tidelift" } ], - "time": "2024-09-08T12:32:26+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/mime", - "version": "v7.1.5", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "711d2e167e8ce65b05aea6b258c449671cdd38ff" + "reference": "caa1e521edb2650b8470918dfe51708c237f0598" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/711d2e167e8ce65b05aea6b258c449671cdd38ff", - "reference": "711d2e167e8ce65b05aea6b258c449671cdd38ff", + "url": "https://api.github.com/repos/symfony/mime/zipball/caa1e521edb2650b8470918dfe51708c237f0598", + "reference": "caa1e521edb2650b8470918dfe51708c237f0598", "shasum": "" }, "require": { @@ -9448,7 +9457,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v7.1.5" + "source": "https://github.com/symfony/mime/tree/v7.1.6" }, "funding": [ { @@ -9464,20 +9473,20 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:28:38+00:00" + "time": "2024-10-25T15:11:02+00:00" }, { "name": "symfony/options-resolver", - "version": "v7.1.1", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55" + "reference": "85e95eeede2d41cd146146e98c9c81d9214cae85" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/47aa818121ed3950acd2b58d1d37d08a94f9bf55", - "reference": "47aa818121ed3950acd2b58d1d37d08a94f9bf55", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/85e95eeede2d41cd146146e98c9c81d9214cae85", + "reference": "85e95eeede2d41cd146146e98c9c81d9214cae85", "shasum": "" }, "require": { @@ -9515,7 +9524,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.1.1" + "source": "https://github.com/symfony/options-resolver/tree/v7.1.6" }, "funding": [ { @@ -9531,7 +9540,7 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/polyfill-ctype", @@ -10251,16 +10260,16 @@ }, { "name": "symfony/process", - "version": "v7.1.5", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "5c03ee6369281177f07f7c68252a280beccba847" + "reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/5c03ee6369281177f07f7c68252a280beccba847", - "reference": "5c03ee6369281177f07f7c68252a280beccba847", + "url": "https://api.github.com/repos/symfony/process/zipball/42783370fda6e538771f7c7a36e9fa2ee3a84892", + "reference": "42783370fda6e538771f7c7a36e9fa2ee3a84892", "shasum": "" }, "require": { @@ -10292,7 +10301,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.1.5" + "source": "https://github.com/symfony/process/tree/v7.1.8" }, "funding": [ { @@ -10308,20 +10317,20 @@ "type": "tidelift" } ], - "time": "2024-09-19T21:48:23+00:00" + "time": "2024-11-06T14:23:19+00:00" }, { "name": "symfony/psr-http-message-bridge", - "version": "v7.1.4", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/psr-http-message-bridge.git", - "reference": "405a7bcd872f1563966f64be19f1362d94ce71ab" + "reference": "f16471bb19f6685b9ccf0a2c03c213840ae68cd6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/405a7bcd872f1563966f64be19f1362d94ce71ab", - "reference": "405a7bcd872f1563966f64be19f1362d94ce71ab", + "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/f16471bb19f6685b9ccf0a2c03c213840ae68cd6", + "reference": "f16471bb19f6685b9ccf0a2c03c213840ae68cd6", "shasum": "" }, "require": { @@ -10375,7 +10384,7 @@ "psr-7" ], "support": { - "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.1.4" + "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.1.6" }, "funding": [ { @@ -10391,20 +10400,20 @@ "type": "tidelift" } ], - "time": "2024-08-15T22:48:53+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/routing", - "version": "v7.1.4", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7" + "reference": "66a2c469f6c22d08603235c46a20007c0701ea0a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", - "reference": "1500aee0094a3ce1c92626ed8cf3c2037e86f5a7", + "url": "https://api.github.com/repos/symfony/routing/zipball/66a2c469f6c22d08603235c46a20007c0701ea0a", + "reference": "66a2c469f6c22d08603235c46a20007c0701ea0a", "shasum": "" }, "require": { @@ -10456,7 +10465,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.1.4" + "source": "https://github.com/symfony/routing/tree/v7.1.6" }, "funding": [ { @@ -10472,7 +10481,7 @@ "type": "tidelift" } ], - "time": "2024-08-29T08:16:25+00:00" + "time": "2024-10-01T08:31:23+00:00" }, { "name": "symfony/service-contracts", @@ -10559,16 +10568,16 @@ }, { "name": "symfony/stopwatch", - "version": "v7.1.1", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d" + "reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", - "reference": "5b75bb1ac2ba1b9d05c47fc4b3046a625377d23d", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/8b4a434e6e7faf6adedffb48783a5c75409a1a05", + "reference": "8b4a434e6e7faf6adedffb48783a5c75409a1a05", "shasum": "" }, "require": { @@ -10601,7 +10610,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v7.1.1" + "source": "https://github.com/symfony/stopwatch/tree/v7.1.6" }, "funding": [ { @@ -10617,20 +10626,20 @@ "type": "tidelift" } ], - "time": "2024-05-31T14:57:53+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/string", - "version": "v7.1.5", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306" + "reference": "591ebd41565f356fcd8b090fe64dbb5878f50281" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/d66f9c343fa894ec2037cc928381df90a7ad4306", - "reference": "d66f9c343fa894ec2037cc928381df90a7ad4306", + "url": "https://api.github.com/repos/symfony/string/zipball/591ebd41565f356fcd8b090fe64dbb5878f50281", + "reference": "591ebd41565f356fcd8b090fe64dbb5878f50281", "shasum": "" }, "require": { @@ -10688,7 +10697,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.1.5" + "source": "https://github.com/symfony/string/tree/v7.1.8" }, "funding": [ { @@ -10704,20 +10713,20 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:28:38+00:00" + "time": "2024-11-13T13:31:21+00:00" }, { "name": "symfony/translation", - "version": "v7.1.5", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "235535e3f84f3dfbdbde0208ede6ca75c3a489ea" + "reference": "b9f72ab14efdb6b772f85041fa12f820dee8d55f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/235535e3f84f3dfbdbde0208ede6ca75c3a489ea", - "reference": "235535e3f84f3dfbdbde0208ede6ca75c3a489ea", + "url": "https://api.github.com/repos/symfony/translation/zipball/b9f72ab14efdb6b772f85041fa12f820dee8d55f", + "reference": "b9f72ab14efdb6b772f85041fa12f820dee8d55f", "shasum": "" }, "require": { @@ -10782,7 +10791,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v7.1.5" + "source": "https://github.com/symfony/translation/tree/v7.1.6" }, "funding": [ { @@ -10798,7 +10807,7 @@ "type": "tidelift" } ], - "time": "2024-09-16T06:30:38+00:00" + "time": "2024-09-28T12:35:13+00:00" }, { "name": "symfony/translation-contracts", @@ -10880,16 +10889,16 @@ }, { "name": "symfony/uid", - "version": "v7.1.5", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "8c7bb8acb933964055215d89f9a9871df0239317" + "reference": "65befb3bb2d503bbffbd08c815aa38b472999917" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/8c7bb8acb933964055215d89f9a9871df0239317", - "reference": "8c7bb8acb933964055215d89f9a9871df0239317", + "url": "https://api.github.com/repos/symfony/uid/zipball/65befb3bb2d503bbffbd08c815aa38b472999917", + "reference": "65befb3bb2d503bbffbd08c815aa38b472999917", "shasum": "" }, "require": { @@ -10934,7 +10943,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v7.1.5" + "source": "https://github.com/symfony/uid/tree/v7.1.6" }, "funding": [ { @@ -10950,20 +10959,20 @@ "type": "tidelift" } ], - "time": "2024-09-17T09:16:35+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "symfony/var-dumper", - "version": "v7.1.5", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "e20e03889539fd4e4211e14d2179226c513c010d" + "reference": "7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/e20e03889539fd4e4211e14d2179226c513c010d", - "reference": "e20e03889539fd4e4211e14d2179226c513c010d", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8", + "reference": "7bb01a47b1b00428d32b5e7b4d3b2d1aa58d3db8", "shasum": "" }, "require": { @@ -11017,7 +11026,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.1.5" + "source": "https://github.com/symfony/var-dumper/tree/v7.1.8" }, "funding": [ { @@ -11033,32 +11042,31 @@ "type": "tidelift" } ], - "time": "2024-09-16T10:07:02+00:00" + "time": "2024-11-08T15:46:42+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.12", + "version": "v7.1.6", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "762ee56b2649659380e0ef4d592d807bc17b7971" + "reference": "3ced3f29e4f0d6bce2170ff26719f1fe9aacc671" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/762ee56b2649659380e0ef4d592d807bc17b7971", - "reference": "762ee56b2649659380e0ef4d592d807bc17b7971", + "url": "https://api.github.com/repos/symfony/yaml/zipball/3ced3f29e4f0d6bce2170ff26719f1fe9aacc671", + "reference": "3ced3f29e4f0d6bce2170ff26719f1fe9aacc671", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<5.4" + "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0" + "symfony/console": "^6.4|^7.0" }, "bin": [ "Resources/bin/yaml-lint" @@ -11089,7 +11097,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.12" + "source": "https://github.com/symfony/yaml/tree/v7.1.6" }, "funding": [ { @@ -11105,7 +11113,7 @@ "type": "tidelift" } ], - "time": "2024-09-17T12:47:12+00:00" + "time": "2024-09-25T14:20:29+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -11162,25 +11170,24 @@ }, { "name": "visus/cuid2", - "version": "2.0.0", + "version": "4.1.0", "source": { "type": "git", "url": "https://github.com/visus-io/php-cuid2.git", - "reference": "907919cadd8dfeb24ffecf7209ec4988fb9b3fc0" + "reference": "17c9b3098d556bb2556a084c948211333cc19c79" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/visus-io/php-cuid2/zipball/907919cadd8dfeb24ffecf7209ec4988fb9b3fc0", - "reference": "907919cadd8dfeb24ffecf7209ec4988fb9b3fc0", + "url": "https://api.github.com/repos/visus-io/php-cuid2/zipball/17c9b3098d556bb2556a084c948211333cc19c79", + "reference": "17c9b3098d556bb2556a084c948211333cc19c79", "shasum": "" }, "require": { - "php": "^8.0" + "php": "^8.1" }, "require-dev": { "ergebnis/composer-normalize": "^2.29", "ext-ctype": "*", - "php-parallel-lint/php-parallel-lint": "^1.3", "phpstan/phpstan": "^1.9", "phpunit/phpunit": "^10.0", "squizlabs/php_codesniffer": "^3.7", @@ -11200,7 +11207,7 @@ }, "notification-url": "https://packagist.org/downloads/", "license": [ - "Apache-2.0" + "MIT" ], "authors": [ { @@ -11215,9 +11222,9 @@ ], "support": { "issues": "https://github.com/visus-io/php-cuid2/issues", - "source": "https://github.com/visus-io/php-cuid2/tree/2.0.0" + "source": "https://github.com/visus-io/php-cuid2/tree/4.1.0" }, - "time": "2023-03-23T19:18:36+00:00" + "time": "2024-05-14T13:23:35+00:00" }, { "name": "vlucas/phpdotenv", @@ -11755,16 +11762,16 @@ }, { "name": "zircote/swagger-php", - "version": "4.11.0", + "version": "4.11.1", "source": { "type": "git", "url": "https://github.com/zircote/swagger-php.git", - "reference": "3b6f3800f4fd6544ada4dce180c6b69eaead7c7c" + "reference": "7df10e8ec47db07c031db317a25bef962b4e5de1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/zircote/swagger-php/zipball/3b6f3800f4fd6544ada4dce180c6b69eaead7c7c", - "reference": "3b6f3800f4fd6544ada4dce180c6b69eaead7c7c", + "url": "https://api.github.com/repos/zircote/swagger-php/zipball/7df10e8ec47db07c031db317a25bef962b4e5de1", + "reference": "7df10e8ec47db07c031db317a25bef962b4e5de1", "shasum": "" }, "require": { @@ -11830,24 +11837,24 @@ ], "support": { "issues": "https://github.com/zircote/swagger-php/issues", - "source": "https://github.com/zircote/swagger-php/tree/4.11.0" + "source": "https://github.com/zircote/swagger-php/tree/4.11.1" }, - "time": "2024-10-09T03:11:12+00:00" + "time": "2024-10-15T19:20:02+00:00" } ], "packages-dev": [ { "name": "barryvdh/laravel-debugbar", - "version": "v3.14.3", + "version": "v3.14.7", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-debugbar.git", - "reference": "c0bee7c08ae2429e4a9ed2bc75679b012db6e3bd" + "reference": "f484b8c9124de0b163da39958331098ffcd4a65e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/c0bee7c08ae2429e4a9ed2bc75679b012db6e3bd", - "reference": "c0bee7c08ae2429e4a9ed2bc75679b012db6e3bd", + "url": "https://api.github.com/repos/barryvdh/laravel-debugbar/zipball/f484b8c9124de0b163da39958331098ffcd4a65e", + "reference": "f484b8c9124de0b163da39958331098ffcd4a65e", "shasum": "" }, "require": { @@ -11906,7 +11913,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-debugbar/issues", - "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.3" + "source": "https://github.com/barryvdh/laravel-debugbar/tree/v3.14.7" }, "funding": [ { @@ -11918,20 +11925,20 @@ "type": "github" } ], - "time": "2024-10-02T09:17:49+00:00" + "time": "2024-11-14T09:12:35+00:00" }, { "name": "brianium/paratest", - "version": "v7.4.3", + "version": "v7.6.0", "source": { "type": "git", "url": "https://github.com/paratestphp/paratest.git", - "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec" + "reference": "68ff89a8de47d086588e391a516d2a5b5fde6254" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/paratestphp/paratest/zipball/64fcfd0e28a6b8078a19dbf9127be2ee645b92ec", - "reference": "64fcfd0e28a6b8078a19dbf9127be2ee645b92ec", + "url": "https://api.github.com/repos/paratestphp/paratest/zipball/68ff89a8de47d086588e391a516d2a5b5fde6254", + "reference": "68ff89a8de47d086588e391a516d2a5b5fde6254", "shasum": "" }, "require": { @@ -11939,31 +11946,30 @@ "ext-pcre": "*", "ext-reflection": "*", "ext-simplexml": "*", - "fidry/cpu-core-counter": "^1.1.0", - "jean85/pretty-package-versions": "^2.0.5", - "php": "~8.2.0 || ~8.3.0", - "phpunit/php-code-coverage": "^10.1.11 || ^11.0.0", - "phpunit/php-file-iterator": "^4.1.0 || ^5.0.0", - "phpunit/php-timer": "^6.0.0 || ^7.0.0", - "phpunit/phpunit": "^10.5.9 || ^11.0.3", - "sebastian/environment": "^6.0.1 || ^7.0.0", - "symfony/console": "^6.4.3 || ^7.0.3", - "symfony/process": "^6.4.3 || ^7.0.3" + "fidry/cpu-core-counter": "^1.2.0", + "jean85/pretty-package-versions": "^2.0.6", + "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", + "sebastian/environment": "^7.2.0", + "symfony/console": "^6.4.11 || ^7.1.5", + "symfony/process": "^6.4.8 || ^7.1.5" }, "require-dev": { "doctrine/coding-standard": "^12.0.0", "ext-pcov": "*", "ext-posix": "*", - "phpstan/phpstan": "^1.10.58", - "phpstan/phpstan-deprecation-rules": "^1.1.4", - "phpstan/phpstan-phpunit": "^1.3.15", - "phpstan/phpstan-strict-rules": "^1.5.2", - "squizlabs/php_codesniffer": "^3.9.0", - "symfony/filesystem": "^6.4.3 || ^7.0.3" + "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" }, "bin": [ "bin/paratest", - "bin/paratest.bat", "bin/paratest_for_phpstorm" ], "type": "library", @@ -12000,7 +12006,7 @@ ], "support": { "issues": "https://github.com/paratestphp/paratest/issues", - "source": "https://github.com/paratestphp/paratest/tree/v7.4.3" + "source": "https://github.com/paratestphp/paratest/tree/v7.6.0" }, "funding": [ { @@ -12012,20 +12018,20 @@ "type": "paypal" } ], - "time": "2024-02-20T07:24:02+00:00" + "time": "2024-10-15T12:38:31+00:00" }, { "name": "fakerphp/faker", - "version": "v1.23.1", + "version": "v1.24.0", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" + "reference": "a136842a532bac9ecd8a1c723852b09915d7db50" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", - "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/a136842a532bac9ecd8a1c723852b09915d7db50", + "reference": "a136842a532bac9ecd8a1c723852b09915d7db50", "shasum": "" }, "require": { @@ -12073,9 +12079,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" + "source": "https://github.com/FakerPHP/Faker/tree/v1.24.0" }, - "time": "2024-01-02T13:46:09+00:00" + "time": "2024-11-07T15:11:20+00:00" }, { "name": "fidry/cpu-core-counter", @@ -12262,16 +12268,16 @@ }, { "name": "laravel/dusk", - "version": "v8.2.8", + "version": "v8.2.11", "source": { "type": "git", "url": "https://github.com/laravel/dusk.git", - "reference": "5bff1e8dd87ec653a2202475377152e5d14fde40" + "reference": "c667db6d8795f0ccc8f63d54a7780ce8a0cc3d3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/dusk/zipball/5bff1e8dd87ec653a2202475377152e5d14fde40", - "reference": "5bff1e8dd87ec653a2202475377152e5d14fde40", + "url": "https://api.github.com/repos/laravel/dusk/zipball/c667db6d8795f0ccc8f63d54a7780ce8a0cc3d3c", + "reference": "c667db6d8795f0ccc8f63d54a7780ce8a0cc3d3c", "shasum": "" }, "require": { @@ -12328,9 +12334,9 @@ ], "support": { "issues": "https://github.com/laravel/dusk/issues", - "source": "https://github.com/laravel/dusk/tree/v8.2.8" + "source": "https://github.com/laravel/dusk/tree/v8.2.11" }, - "time": "2024-10-04T14:02:20+00:00" + "time": "2024-11-07T21:51:32+00:00" }, { "name": "laravel/pint", @@ -12400,16 +12406,16 @@ }, { "name": "laravel/telescope", - "version": "v5.2.4", + "version": "v5.2.5", "source": { "type": "git", "url": "https://github.com/laravel/telescope.git", - "reference": "749369e996611d803e7c1b57929b482dd676008d" + "reference": "f68386a8d816c9e3a011b8301bfd263213bf00d4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/telescope/zipball/749369e996611d803e7c1b57929b482dd676008d", - "reference": "749369e996611d803e7c1b57929b482dd676008d", + "url": "https://api.github.com/repos/laravel/telescope/zipball/f68386a8d816c9e3a011b8301bfd263213bf00d4", + "reference": "f68386a8d816c9e3a011b8301bfd263213bf00d4", "shasum": "" }, "require": { @@ -12463,22 +12469,22 @@ ], "support": { "issues": "https://github.com/laravel/telescope/issues", - "source": "https://github.com/laravel/telescope/tree/v5.2.4" + "source": "https://github.com/laravel/telescope/tree/v5.2.5" }, - "time": "2024-10-29T15:35:13+00:00" + "time": "2024-10-31T17:06:07+00:00" }, { "name": "maximebf/debugbar", - "version": "v1.23.2", + "version": "v1.23.3", "source": { "type": "git", "url": "https://github.com/maximebf/php-debugbar.git", - "reference": "689720d724c771ac4add859056744b7b3f2406da" + "reference": "687400043d77943ef95e8417cb44e1673ee57844" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/689720d724c771ac4add859056744b7b3f2406da", - "reference": "689720d724c771ac4add859056744b7b3f2406da", + "url": "https://api.github.com/repos/maximebf/php-debugbar/zipball/687400043d77943ef95e8417cb44e1673ee57844", + "reference": "687400043d77943ef95e8417cb44e1673ee57844", "shasum": "" }, "require": { @@ -12531,9 +12537,9 @@ ], "support": { "issues": "https://github.com/maximebf/php-debugbar/issues", - "source": "https://github.com/maximebf/php-debugbar/tree/v1.23.2" + "source": "https://github.com/maximebf/php-debugbar/tree/v1.23.3" }, - "time": "2024-09-16T11:23:09+00:00" + "time": "2024-10-29T12:24:25+00:00" }, { "name": "mockery/mockery", @@ -12620,16 +12626,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.12.0", + "version": "1.12.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c" + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", - "reference": "3a6b9a42cd8f8771bd4295d13e1423fa7f3d942c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/123267b2c49fbf30d78a7b2d333f6be754b94845", + "reference": "123267b2c49fbf30d78a7b2d333f6be754b94845", "shasum": "" }, "require": { @@ -12668,7 +12674,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.12.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.12.1" }, "funding": [ { @@ -12676,27 +12682,27 @@ "type": "tidelift" } ], - "time": "2024-06-12T14:39:25+00:00" + "time": "2024-11-08T17:47:46+00:00" }, { "name": "nunomaduro/collision", - "version": "v8.4.0", + "version": "v8.5.0", "source": { "type": "git", "url": "https://github.com/nunomaduro/collision.git", - "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a" + "reference": "f5c101b929c958e849a633283adff296ed5f38f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nunomaduro/collision/zipball/e7d1aa8ed753f63fa816932bbc89678238843b4a", - "reference": "e7d1aa8ed753f63fa816932bbc89678238843b4a", + "url": "https://api.github.com/repos/nunomaduro/collision/zipball/f5c101b929c958e849a633283adff296ed5f38f5", + "reference": "f5c101b929c958e849a633283adff296ed5f38f5", "shasum": "" }, "require": { - "filp/whoops": "^2.15.4", - "nunomaduro/termwind": "^2.0.1", + "filp/whoops": "^2.16.0", + "nunomaduro/termwind": "^2.1.0", "php": "^8.2.0", - "symfony/console": "^7.1.3" + "symfony/console": "^7.1.5" }, "conflict": { "laravel/framework": "<11.0.0 || >=12.0.0", @@ -12704,14 +12710,14 @@ }, "require-dev": { "larastan/larastan": "^2.9.8", - "laravel/framework": "^11.19.0", - "laravel/pint": "^1.17.1", - "laravel/sail": "^1.31.0", - "laravel/sanctum": "^4.0.2", - "laravel/tinker": "^2.9.0", - "orchestra/testbench-core": "^9.2.3", - "pestphp/pest": "^2.35.0 || ^3.0.0", - "sebastian/environment": "^6.1.0 || ^7.0.0" + "laravel/framework": "^11.28.0", + "laravel/pint": "^1.18.1", + "laravel/sail": "^1.36.0", + "laravel/sanctum": "^4.0.3", + "laravel/tinker": "^2.10.0", + "orchestra/testbench-core": "^9.5.3", + "pestphp/pest": "^2.36.0 || ^3.4.0", + "sebastian/environment": "^6.1.0 || ^7.2.0" }, "type": "library", "extra": { @@ -12773,40 +12779,42 @@ "type": "patreon" } ], - "time": "2024-08-03T15:32:23+00:00" + "time": "2024-10-15T16:06:32+00:00" }, { "name": "pestphp/pest", - "version": "v2.35.1", + "version": "v3.5.1", "source": { "type": "git", "url": "https://github.com/pestphp/pest.git", - "reference": "b13acb630df52c06123588d321823c31fc685545" + "reference": "179d46ce97d52bcb3f791449ae94025c3f32e3e3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest/zipball/b13acb630df52c06123588d321823c31fc685545", - "reference": "b13acb630df52c06123588d321823c31fc685545", + "url": "https://api.github.com/repos/pestphp/pest/zipball/179d46ce97d52bcb3f791449ae94025c3f32e3e3", + "reference": "179d46ce97d52bcb3f791449ae94025c3f32e3e3", "shasum": "" }, "require": { - "brianium/paratest": "^7.3.1", - "nunomaduro/collision": "^7.10.0|^8.4.0", - "nunomaduro/termwind": "^1.15.1|^2.0.1", - "pestphp/pest-plugin": "^2.1.1", - "pestphp/pest-plugin-arch": "^2.7.0", - "php": "^8.1.0", - "phpunit/phpunit": "^10.5.17" + "brianium/paratest": "^7.6.0", + "nunomaduro/collision": "^8.5.0", + "nunomaduro/termwind": "^2.2.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" }, "conflict": { - "phpunit/phpunit": ">10.5.17", - "sebastian/exporter": "<5.1.0", + "filp/whoops": "<2.16.0", + "phpunit/phpunit": ">11.4.3", + "sebastian/exporter": "<6.0.0", "webmozart/assert": "<1.11.0" }, "require-dev": { - "pestphp/pest-dev-tools": "^2.16.0", - "pestphp/pest-plugin-type-coverage": "^2.8.5", - "symfony/process": "^6.4.0|^7.1.3" + "pestphp/pest-dev-tools": "^3.3.0", + "pestphp/pest-plugin-type-coverage": "^3.1.0", + "symfony/process": "^7.1.6" }, "bin": [ "bin/pest" @@ -12815,6 +12823,8 @@ "extra": { "pest": { "plugins": [ + "Pest\\Mutate\\Plugins\\Mutate", + "Pest\\Plugins\\Configuration", "Pest\\Plugins\\Bail", "Pest\\Plugins\\Cache", "Pest\\Plugins\\Coverage", @@ -12869,7 +12879,7 @@ ], "support": { "issues": "https://github.com/pestphp/pest/issues", - "source": "https://github.com/pestphp/pest/tree/v2.35.1" + "source": "https://github.com/pestphp/pest/tree/v3.5.1" }, "funding": [ { @@ -12881,34 +12891,34 @@ "type": "github" } ], - "time": "2024-08-20T21:41:50+00:00" + "time": "2024-10-31T16:12:45+00:00" }, { "name": "pestphp/pest-plugin", - "version": "v2.1.1", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/pestphp/pest-plugin.git", - "reference": "e05d2859e08c2567ee38ce8b005d044e72648c0b" + "reference": "e79b26c65bc11c41093b10150c1341cc5cdbea83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest-plugin/zipball/e05d2859e08c2567ee38ce8b005d044e72648c0b", - "reference": "e05d2859e08c2567ee38ce8b005d044e72648c0b", + "url": "https://api.github.com/repos/pestphp/pest-plugin/zipball/e79b26c65bc11c41093b10150c1341cc5cdbea83", + "reference": "e79b26c65bc11c41093b10150c1341cc5cdbea83", "shasum": "" }, "require": { "composer-plugin-api": "^2.0.0", "composer-runtime-api": "^2.2.2", - "php": "^8.1" + "php": "^8.2" }, "conflict": { - "pestphp/pest": "<2.2.3" + "pestphp/pest": "<3.0.0" }, "require-dev": { - "composer/composer": "^2.5.8", - "pestphp/pest": "^2.16.0", - "pestphp/pest-dev-tools": "^2.16.0" + "composer/composer": "^2.7.9", + "pestphp/pest": "^3.0.0", + "pestphp/pest-dev-tools": "^3.0.0" }, "type": "composer-plugin", "extra": { @@ -12935,7 +12945,7 @@ "unit" ], "support": { - "source": "https://github.com/pestphp/pest-plugin/tree/v2.1.1" + "source": "https://github.com/pestphp/pest-plugin/tree/v3.0.0" }, "funding": [ { @@ -12951,31 +12961,30 @@ "type": "patreon" } ], - "time": "2023-08-22T08:40:06+00:00" + "time": "2024-09-08T23:21:41+00:00" }, { "name": "pestphp/pest-plugin-arch", - "version": "v2.7.0", + "version": "v3.0.0", "source": { "type": "git", "url": "https://github.com/pestphp/pest-plugin-arch.git", - "reference": "d23b2d7498475354522c3818c42ef355dca3fcda" + "reference": "0a27e55a270cfe73d8cb70551b91002ee2cb64b0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/d23b2d7498475354522c3818c42ef355dca3fcda", - "reference": "d23b2d7498475354522c3818c42ef355dca3fcda", + "url": "https://api.github.com/repos/pestphp/pest-plugin-arch/zipball/0a27e55a270cfe73d8cb70551b91002ee2cb64b0", + "reference": "0a27e55a270cfe73d8cb70551b91002ee2cb64b0", "shasum": "" }, "require": { - "nunomaduro/collision": "^7.10.0|^8.1.0", - "pestphp/pest-plugin": "^2.1.1", - "php": "^8.1", + "pestphp/pest-plugin": "^3.0.0", + "php": "^8.2", "ta-tikoma/phpunit-architecture-test": "^0.8.4" }, "require-dev": { - "pestphp/pest": "^2.33.0", - "pestphp/pest-dev-tools": "^2.16.0" + "pestphp/pest": "^3.0.0", + "pestphp/pest-dev-tools": "^3.0.0" }, "type": "library", "extra": { @@ -13010,7 +13019,7 @@ "unit" ], "support": { - "source": "https://github.com/pestphp/pest-plugin-arch/tree/v2.7.0" + "source": "https://github.com/pestphp/pest-plugin-arch/tree/v3.0.0" }, "funding": [ { @@ -13022,7 +13031,79 @@ "type": "github" } ], - "time": "2024-01-26T09:46:42+00:00" + "time": "2024-09-08T23:23:55+00:00" + }, + { + "name": "pestphp/pest-plugin-mutate", + "version": "v3.0.5", + "source": { + "type": "git", + "url": "https://github.com/pestphp/pest-plugin-mutate.git", + "reference": "e10dbdc98c9e2f3890095b4fe2144f63a5717e08" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/pestphp/pest-plugin-mutate/zipball/e10dbdc98c9e2f3890095b4fe2144f63a5717e08", + "reference": "e10dbdc98c9e2f3890095b4fe2144f63a5717e08", + "shasum": "" + }, + "require": { + "nikic/php-parser": "^5.2.0", + "pestphp/pest-plugin": "^3.0.0", + "php": "^8.2", + "psr/simple-cache": "^3.0.0" + }, + "require-dev": { + "pestphp/pest": "^3.0.8", + "pestphp/pest-dev-tools": "^3.0.0", + "pestphp/pest-plugin-type-coverage": "^3.0.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Pest\\Mutate\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Sandro Gehri", + "email": "sandrogehri@gmail.com" + } + ], + "description": "Mutates your code to find untested cases", + "keywords": [ + "framework", + "mutate", + "mutation", + "pest", + "php", + "plugin", + "test", + "testing", + "unit" + ], + "support": { + "source": "https://github.com/pestphp/pest-plugin-mutate/tree/v3.0.5" + }, + "funding": [ + { + "url": "https://www.paypal.com/paypalme/enunomaduro", + "type": "custom" + }, + { + "url": "https://github.com/gehrisandro", + "type": "github" + }, + { + "url": "https://github.com/nunomaduro", + "type": "github" + } + ], + "time": "2024-09-22T07:54:40+00:00" }, { "name": "phar-io/manifest", @@ -13208,101 +13289,37 @@ }, "time": "2023-10-20T12:21:20+00:00" }, - { - "name": "phpdocumentor/reflection-docblock", - "version": "5.4.1", - "source": { - "type": "git", - "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", - "reference": "9d07b3f7fdcf5efec5d1609cba3c19c5ea2bdc9c", - "shasum": "" - }, - "require": { - "doctrine/deprecations": "^1.1", - "ext-filter": "*", - "php": "^7.4 || ^8.0", - "phpdocumentor/reflection-common": "^2.2", - "phpdocumentor/type-resolver": "^1.7", - "phpstan/phpdoc-parser": "^1.7", - "webmozart/assert": "^1.9.1" - }, - "require-dev": { - "mockery/mockery": "~1.3.5", - "phpstan/extension-installer": "^1.1", - "phpstan/phpstan": "^1.8", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-webmozart-assert": "^1.2", - "phpunit/phpunit": "^9.5", - "vimeo/psalm": "^5.13" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - } - }, - "autoload": { - "psr-4": { - "phpDocumentor\\Reflection\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Mike van Riel", - "email": "me@mikevanriel.com" - }, - { - "name": "Jaap van Otterdijk", - "email": "opensource@ijaap.nl" - } - ], - "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.4.1" - }, - "time": "2024-05-21T05:55:05+00:00" - }, { "name": "phpunit/php-code-coverage", - "version": "10.1.16", + "version": "11.0.7", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77" + "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/7e308268858ed6baedc8704a304727d20bc07c77", - "reference": "7e308268858ed6baedc8704a304727d20bc07c77", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7f08030e8811582cc459871d28d6f5a1a4d35ca", + "reference": "f7f08030e8811582cc459871d28d6f5a1a4d35ca", "shasum": "" }, "require": { "ext-dom": "*", "ext-libxml": "*", "ext-xmlwriter": "*", - "nikic/php-parser": "^4.19.1 || ^5.1.0", - "php": ">=8.1", - "phpunit/php-file-iterator": "^4.1.0", - "phpunit/php-text-template": "^3.0.1", - "sebastian/code-unit-reverse-lookup": "^3.0.0", - "sebastian/complexity": "^3.2.0", - "sebastian/environment": "^6.1.0", - "sebastian/lines-of-code": "^2.0.2", - "sebastian/version": "^4.0.1", + "nikic/php-parser": "^5.3.1", + "php": ">=8.2", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-text-template": "^4.0.1", + "sebastian/code-unit-reverse-lookup": "^4.0.1", + "sebastian/complexity": "^4.0.1", + "sebastian/environment": "^7.2.0", + "sebastian/lines-of-code": "^3.0.1", + "sebastian/version": "^5.0.2", "theseer/tokenizer": "^1.2.3" }, "require-dev": { - "phpunit/phpunit": "^10.1" + "phpunit/phpunit": "^11.4.1" }, "suggest": { "ext-pcov": "PHP extension that provides line coverage", @@ -13311,7 +13328,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.1.x-dev" + "dev-main": "11.0.x-dev" } }, "autoload": { @@ -13340,7 +13357,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/10.1.16" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.7" }, "funding": [ { @@ -13348,32 +13365,32 @@ "type": "github" } ], - "time": "2024-08-22T04:31:57+00:00" + "time": "2024-10-09T06:21:38+00:00" }, { "name": "phpunit/php-file-iterator", - "version": "4.1.0", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-file-iterator.git", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c" + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/a95037b6d9e608ba092da1b23931e537cadc3c3c", - "reference": "a95037b6d9e608ba092da1b23931e537cadc3c3c", + "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/118cfaaa8bc5aef3287bf315b6060b1174754af6", + "reference": "118cfaaa8bc5aef3287bf315b6060b1174754af6", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -13401,7 +13418,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", "security": "https://github.com/sebastianbergmann/php-file-iterator/security/policy", - "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/4.1.0" + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/5.1.0" }, "funding": [ { @@ -13409,28 +13426,28 @@ "type": "github" } ], - "time": "2023-08-31T06:24:48+00:00" + "time": "2024-08-27T05:02:59+00:00" }, { "name": "phpunit/php-invoker", - "version": "4.0.0", + "version": "5.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-invoker.git", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7" + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", - "reference": "f5e568ba02fa5ba0ddd0f618391d5a9ea50b06d7", + "url": "https://api.github.com/repos/sebastianbergmann/php-invoker/zipball/c1ca3814734c07492b3d4c5f794f4b0995333da2", + "reference": "c1ca3814734c07492b3d4c5f794f4b0995333da2", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { "ext-pcntl": "*", - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-pcntl": "*" @@ -13438,7 +13455,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -13464,7 +13481,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-invoker/issues", - "source": "https://github.com/sebastianbergmann/php-invoker/tree/4.0.0" + "security": "https://github.com/sebastianbergmann/php-invoker/security/policy", + "source": "https://github.com/sebastianbergmann/php-invoker/tree/5.0.1" }, "funding": [ { @@ -13472,32 +13490,32 @@ "type": "github" } ], - "time": "2023-02-03T06:56:09+00:00" + "time": "2024-07-03T05:07:44+00:00" }, { "name": "phpunit/php-text-template", - "version": "3.0.1", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-text-template.git", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748" + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/0c7b06ff49e3d5072f057eb1fa59258bf287a748", - "reference": "0c7b06ff49e3d5072f057eb1fa59258bf287a748", + "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/3e0404dc6b300e6bf56415467ebcb3fe4f33e964", + "reference": "3e0404dc6b300e6bf56415467ebcb3fe4f33e964", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -13524,7 +13542,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-text-template/issues", "security": "https://github.com/sebastianbergmann/php-text-template/security/policy", - "source": "https://github.com/sebastianbergmann/php-text-template/tree/3.0.1" + "source": "https://github.com/sebastianbergmann/php-text-template/tree/4.0.1" }, "funding": [ { @@ -13532,32 +13550,32 @@ "type": "github" } ], - "time": "2023-08-31T14:07:24+00:00" + "time": "2024-07-03T05:08:43+00:00" }, { "name": "phpunit/php-timer", - "version": "6.0.0", + "version": "7.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-timer.git", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d" + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/e2a2d67966e740530f4a3343fe2e030ffdc1161d", - "reference": "e2a2d67966e740530f4a3343fe2e030ffdc1161d", + "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", + "reference": "3b415def83fbcb41f991d9ebf16ae4ad8b7837b3", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -13583,7 +13601,8 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/php-timer/issues", - "source": "https://github.com/sebastianbergmann/php-timer/tree/6.0.0" + "security": "https://github.com/sebastianbergmann/php-timer/security/policy", + "source": "https://github.com/sebastianbergmann/php-timer/tree/7.0.1" }, "funding": [ { @@ -13591,20 +13610,20 @@ "type": "github" } ], - "time": "2023-02-03T06:57:52+00:00" + "time": "2024-07-03T05:09:35+00:00" }, { "name": "phpunit/phpunit", - "version": "10.5.17", + "version": "11.4.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "c1f736a473d21957ead7e94fcc029f571895abf5" + "reference": "e8e8ed1854de5d36c088ec1833beae40d2dedd76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/c1f736a473d21957ead7e94fcc029f571895abf5", - "reference": "c1f736a473d21957ead7e94fcc029f571895abf5", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/e8e8ed1854de5d36c088ec1833beae40d2dedd76", + "reference": "e8e8ed1854de5d36c088ec1833beae40d2dedd76", "shasum": "" }, "require": { @@ -13614,26 +13633,25 @@ "ext-mbstring": "*", "ext-xml": "*", "ext-xmlwriter": "*", - "myclabs/deep-copy": "^1.10.1", - "phar-io/manifest": "^2.0.3", - "phar-io/version": "^3.0.2", - "php": ">=8.1", - "phpunit/php-code-coverage": "^10.1.5", - "phpunit/php-file-iterator": "^4.0", - "phpunit/php-invoker": "^4.0", - "phpunit/php-text-template": "^3.0", - "phpunit/php-timer": "^6.0", - "sebastian/cli-parser": "^2.0", - "sebastian/code-unit": "^2.0", - "sebastian/comparator": "^5.0", - "sebastian/diff": "^5.0", - "sebastian/environment": "^6.0", - "sebastian/exporter": "^5.1", - "sebastian/global-state": "^6.0.1", - "sebastian/object-enumerator": "^5.0", - "sebastian/recursion-context": "^5.0", - "sebastian/type": "^4.0", - "sebastian/version": "^4.0" + "myclabs/deep-copy": "^1.12.0", + "phar-io/manifest": "^2.0.4", + "phar-io/version": "^3.2.1", + "php": ">=8.2", + "phpunit/php-code-coverage": "^11.0.7", + "phpunit/php-file-iterator": "^5.1.0", + "phpunit/php-invoker": "^5.0.1", + "phpunit/php-text-template": "^4.0.1", + "phpunit/php-timer": "^7.0.1", + "sebastian/cli-parser": "^3.0.2", + "sebastian/code-unit": "^3.0.1", + "sebastian/comparator": "^6.1.1", + "sebastian/diff": "^6.0.2", + "sebastian/environment": "^7.2.0", + "sebastian/exporter": "^6.1.3", + "sebastian/global-state": "^7.0.2", + "sebastian/object-enumerator": "^6.0.1", + "sebastian/type": "^5.1.0", + "sebastian/version": "^5.0.2" }, "suggest": { "ext-soap": "To be able to generate mocks based on WSDL files" @@ -13644,7 +13662,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "10.5-dev" + "dev-main": "11.4-dev" } }, "autoload": { @@ -13676,7 +13694,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.17" + "source": "https://github.com/sebastianbergmann/phpunit/tree/11.4.3" }, "funding": [ { @@ -13692,32 +13710,32 @@ "type": "tidelift" } ], - "time": "2024-04-05T04:39:01+00:00" + "time": "2024-10-28T13:07:50+00:00" }, { "name": "sebastian/cli-parser", - "version": "2.0.1", + "version": "3.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", - "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/15c5dd40dc4f38794d383bb95465193f5e0ae180", + "reference": "15c5dd40dc4f38794d383bb95465193f5e0ae180", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -13741,7 +13759,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" + "source": "https://github.com/sebastianbergmann/cli-parser/tree/3.0.2" }, "funding": [ { @@ -13749,32 +13767,32 @@ "type": "github" } ], - "time": "2024-03-02T07:12:49+00:00" + "time": "2024-07-03T04:41:36+00:00" }, { "name": "sebastian/code-unit", - "version": "2.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit.git", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503" + "reference": "6bb7d09d6623567178cf54126afa9c2310114268" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/a81fee9eef0b7a76af11d121767abc44c104e503", - "reference": "a81fee9eef0b7a76af11d121767abc44c104e503", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit/zipball/6bb7d09d6623567178cf54126afa9c2310114268", + "reference": "6bb7d09d6623567178cf54126afa9c2310114268", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -13797,7 +13815,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit", "support": { "issues": "https://github.com/sebastianbergmann/code-unit/issues", - "source": "https://github.com/sebastianbergmann/code-unit/tree/2.0.0" + "security": "https://github.com/sebastianbergmann/code-unit/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit/tree/3.0.1" }, "funding": [ { @@ -13805,32 +13824,32 @@ "type": "github" } ], - "time": "2023-02-03T06:58:43+00:00" + "time": "2024-07-03T04:44:28+00:00" }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "3.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d" + "reference": "183a9b2632194febd219bb9246eee421dad8d45e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", - "reference": "5e3a687f7d8ae33fb362c5c0743794bbb2420a1d", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/183a9b2632194febd219bb9246eee421dad8d45e", + "reference": "183a9b2632194febd219bb9246eee421dad8d45e", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -13852,7 +13871,8 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/3.0.0" + "security": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/security/policy", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/4.0.1" }, "funding": [ { @@ -13860,36 +13880,36 @@ "type": "github" } ], - "time": "2023-02-03T06:59:15+00:00" + "time": "2024-07-03T04:45:54+00:00" }, { "name": "sebastian/comparator", - "version": "5.0.2", + "version": "6.2.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53" + "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", - "reference": "2d3e04c3b4c1e84a5e7382221ad8883c8fbc4f53", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/43d129d6a0f81c78bee378b46688293eb7ea3739", + "reference": "43d129d6a0f81c78bee378b46688293eb7ea3739", "shasum": "" }, "require": { "ext-dom": "*", "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/diff": "^5.0", - "sebastian/exporter": "^5.0" + "php": ">=8.2", + "sebastian/diff": "^6.0", + "sebastian/exporter": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^10.4" + "phpunit/phpunit": "^11.4" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.2-dev" } }, "autoload": { @@ -13929,7 +13949,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", "security": "https://github.com/sebastianbergmann/comparator/security/policy", - "source": "https://github.com/sebastianbergmann/comparator/tree/5.0.2" + "source": "https://github.com/sebastianbergmann/comparator/tree/6.2.1" }, "funding": [ { @@ -13937,33 +13957,33 @@ "type": "github" } ], - "time": "2024-08-12T06:03:08+00:00" + "time": "2024-10-31T05:30:08+00:00" }, { "name": "sebastian/complexity", - "version": "3.2.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/complexity.git", - "reference": "68ff824baeae169ec9f2137158ee529584553799" + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/68ff824baeae169ec9f2137158ee529584553799", - "reference": "68ff824baeae169ec9f2137158ee529584553799", + "url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/ee41d384ab1906c68852636b6de493846e13e5a0", + "reference": "ee41d384ab1906c68852636b6de493846e13e5a0", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.2-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -13987,7 +14007,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/complexity/issues", "security": "https://github.com/sebastianbergmann/complexity/security/policy", - "source": "https://github.com/sebastianbergmann/complexity/tree/3.2.0" + "source": "https://github.com/sebastianbergmann/complexity/tree/4.0.1" }, "funding": [ { @@ -13995,33 +14015,33 @@ "type": "github" } ], - "time": "2023-12-21T08:37:17+00:00" + "time": "2024-07-03T04:49:50+00:00" }, { "name": "sebastian/diff", - "version": "5.1.1", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", - "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/b4ccd857127db5d41a5b676f24b51371d76d8544", + "reference": "b4ccd857127db5d41a5b676f24b51371d76d8544", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0", - "symfony/process": "^6.4" + "phpunit/phpunit": "^11.0", + "symfony/process": "^4.2 || ^5" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -14054,7 +14074,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" + "source": "https://github.com/sebastianbergmann/diff/tree/6.0.2" }, "funding": [ { @@ -14062,27 +14082,27 @@ "type": "github" } ], - "time": "2024-03-02T07:15:17+00:00" + "time": "2024-07-03T04:53:05+00:00" }, { "name": "sebastian/environment", - "version": "6.1.0", + "version": "7.2.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/environment.git", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984" + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/8074dbcd93529b357029f5cc5058fd3e43666984", - "reference": "8074dbcd93529b357029f5cc5058fd3e43666984", + "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", + "reference": "855f3ae0ab316bbafe1ba4e16e9f3c078d24a0c5", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "suggest": { "ext-posix": "*" @@ -14090,7 +14110,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-main": "6.1-dev" + "dev-main": "7.2-dev" } }, "autoload": { @@ -14118,7 +14138,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/environment/issues", "security": "https://github.com/sebastianbergmann/environment/security/policy", - "source": "https://github.com/sebastianbergmann/environment/tree/6.1.0" + "source": "https://github.com/sebastianbergmann/environment/tree/7.2.0" }, "funding": [ { @@ -14126,34 +14146,34 @@ "type": "github" } ], - "time": "2024-03-23T08:47:14+00:00" + "time": "2024-07-03T04:54:44+00:00" }, { "name": "sebastian/exporter", - "version": "5.1.2", + "version": "6.1.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf" + "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", - "reference": "955288482d97c19a372d3f31006ab3f37da47adf", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", + "reference": "c414673eee9a8f9d51bbf8d61fc9e3ef1e85b20e", "shasum": "" }, "require": { "ext-mbstring": "*", - "php": ">=8.1", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.2" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.1-dev" + "dev-main": "6.1-dev" } }, "autoload": { @@ -14196,7 +14216,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" + "source": "https://github.com/sebastianbergmann/exporter/tree/6.1.3" }, "funding": [ { @@ -14204,35 +14224,35 @@ "type": "github" } ], - "time": "2024-03-02T07:17:12+00:00" + "time": "2024-07-03T04:56:19+00:00" }, { "name": "sebastian/global-state", - "version": "6.0.2", + "version": "7.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" + "reference": "3be331570a721f9a4b5917f4209773de17f747d7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", - "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/3be331570a721f9a4b5917f4209773de17f747d7", + "reference": "3be331570a721f9a4b5917f4209773de17f747d7", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { "ext-dom": "*", - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "6.0-dev" + "dev-main": "7.0-dev" } }, "autoload": { @@ -14258,7 +14278,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/7.0.2" }, "funding": [ { @@ -14266,33 +14286,33 @@ "type": "github" } ], - "time": "2024-03-02T07:19:19+00:00" + "time": "2024-07-03T04:57:36+00:00" }, { "name": "sebastian/lines-of-code", - "version": "2.0.2", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/lines-of-code.git", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0" + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/856e7f6a75a84e339195d48c556f23be2ebf75d0", - "reference": "856e7f6a75a84e339195d48c556f23be2ebf75d0", + "url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/d36ad0d782e5756913e42ad87cb2890f4ffe467a", + "reference": "d36ad0d782e5756913e42ad87cb2890f4ffe467a", "shasum": "" }, "require": { - "nikic/php-parser": "^4.18 || ^5.0", - "php": ">=8.1" + "nikic/php-parser": "^5.0", + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.0-dev" + "dev-main": "3.0-dev" } }, "autoload": { @@ -14316,7 +14336,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/lines-of-code/issues", "security": "https://github.com/sebastianbergmann/lines-of-code/security/policy", - "source": "https://github.com/sebastianbergmann/lines-of-code/tree/2.0.2" + "source": "https://github.com/sebastianbergmann/lines-of-code/tree/3.0.1" }, "funding": [ { @@ -14324,34 +14344,34 @@ "type": "github" } ], - "time": "2023-12-21T08:38:20+00:00" + "time": "2024-07-03T04:58:38+00:00" }, { "name": "sebastian/object-enumerator", - "version": "5.0.0", + "version": "6.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-enumerator.git", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906" + "reference": "f5b498e631a74204185071eb41f33f38d64608aa" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/202d0e344a580d7f7d04b3fafce6933e59dae906", - "reference": "202d0e344a580d7f7d04b3fafce6933e59dae906", + "url": "https://api.github.com/repos/sebastianbergmann/object-enumerator/zipball/f5b498e631a74204185071eb41f33f38d64608aa", + "reference": "f5b498e631a74204185071eb41f33f38d64608aa", "shasum": "" }, "require": { - "php": ">=8.1", - "sebastian/object-reflector": "^3.0", - "sebastian/recursion-context": "^5.0" + "php": ">=8.2", + "sebastian/object-reflector": "^4.0", + "sebastian/recursion-context": "^6.0" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -14373,7 +14393,8 @@ "homepage": "https://github.com/sebastianbergmann/object-enumerator/", "support": { "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", - "source": "https://github.com/sebastianbergmann/object-enumerator/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/object-enumerator/security/policy", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/6.0.1" }, "funding": [ { @@ -14381,32 +14402,32 @@ "type": "github" } ], - "time": "2023-02-03T07:08:32+00:00" + "time": "2024-07-03T05:00:13+00:00" }, { "name": "sebastian/object-reflector", - "version": "3.0.0", + "version": "4.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/object-reflector.git", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957" + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/24ed13d98130f0e7122df55d06c5c4942a577957", - "reference": "24ed13d98130f0e7122df55d06c5c4942a577957", + "url": "https://api.github.com/repos/sebastianbergmann/object-reflector/zipball/6e1a43b411b2ad34146dee7524cb13a068bb35f9", + "reference": "6e1a43b411b2ad34146dee7524cb13a068bb35f9", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "3.0-dev" + "dev-main": "4.0-dev" } }, "autoload": { @@ -14428,7 +14449,8 @@ "homepage": "https://github.com/sebastianbergmann/object-reflector/", "support": { "issues": "https://github.com/sebastianbergmann/object-reflector/issues", - "source": "https://github.com/sebastianbergmann/object-reflector/tree/3.0.0" + "security": "https://github.com/sebastianbergmann/object-reflector/security/policy", + "source": "https://github.com/sebastianbergmann/object-reflector/tree/4.0.1" }, "funding": [ { @@ -14436,32 +14458,32 @@ "type": "github" } ], - "time": "2023-02-03T07:06:18+00:00" + "time": "2024-07-03T05:01:32+00:00" }, { "name": "sebastian/recursion-context", - "version": "5.0.0", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/recursion-context.git", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712" + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/05909fb5bc7df4c52992396d0116aed689f93712", - "reference": "05909fb5bc7df4c52992396d0116aed689f93712", + "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/694d156164372abbd149a4b85ccda2e4670c0e16", + "reference": "694d156164372abbd149a4b85ccda2e4670c0e16", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.0" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "5.0-dev" + "dev-main": "6.0-dev" } }, "autoload": { @@ -14491,7 +14513,8 @@ "homepage": "https://github.com/sebastianbergmann/recursion-context", "support": { "issues": "https://github.com/sebastianbergmann/recursion-context/issues", - "source": "https://github.com/sebastianbergmann/recursion-context/tree/5.0.0" + "security": "https://github.com/sebastianbergmann/recursion-context/security/policy", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/6.0.2" }, "funding": [ { @@ -14499,32 +14522,32 @@ "type": "github" } ], - "time": "2023-02-03T07:05:40+00:00" + "time": "2024-07-03T05:10:34+00:00" }, { "name": "sebastian/type", - "version": "4.0.0", + "version": "5.1.0", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/type.git", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf" + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/462699a16464c3944eefc02ebdd77882bd3925bf", - "reference": "462699a16464c3944eefc02ebdd77882bd3925bf", + "url": "https://api.github.com/repos/sebastianbergmann/type/zipball/461b9c5da241511a2a0e8f240814fb23ce5c0aac", + "reference": "461b9c5da241511a2a0e8f240814fb23ce5c0aac", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "require-dev": { - "phpunit/phpunit": "^10.0" + "phpunit/phpunit": "^11.3" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.1-dev" } }, "autoload": { @@ -14547,7 +14570,8 @@ "homepage": "https://github.com/sebastianbergmann/type", "support": { "issues": "https://github.com/sebastianbergmann/type/issues", - "source": "https://github.com/sebastianbergmann/type/tree/4.0.0" + "security": "https://github.com/sebastianbergmann/type/security/policy", + "source": "https://github.com/sebastianbergmann/type/tree/5.1.0" }, "funding": [ { @@ -14555,29 +14579,29 @@ "type": "github" } ], - "time": "2023-02-03T07:10:45+00:00" + "time": "2024-09-17T13:12:04+00:00" }, { "name": "sebastian/version", - "version": "4.0.1", + "version": "5.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/version.git", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17" + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c51fa83a5d8f43f1402e3f32a005e6262244ef17", - "reference": "c51fa83a5d8f43f1402e3f32a005e6262244ef17", + "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/c687e3387b99f5b03b6caa64c74b63e2936ff874", + "reference": "c687e3387b99f5b03b6caa64c74b63e2936ff874", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "4.0-dev" + "dev-main": "5.0-dev" } }, "autoload": { @@ -14600,7 +14624,8 @@ "homepage": "https://github.com/sebastianbergmann/version", "support": { "issues": "https://github.com/sebastianbergmann/version/issues", - "source": "https://github.com/sebastianbergmann/version/tree/4.0.1" + "security": "https://github.com/sebastianbergmann/version/security/policy", + "source": "https://github.com/sebastianbergmann/version/tree/5.0.2" }, "funding": [ { @@ -14608,20 +14633,20 @@ "type": "github" } ], - "time": "2023-02-07T11:34:05+00:00" + "time": "2024-10-09T05:16:32+00:00" }, { "name": "serversideup/spin", - "version": "v1.1.0", + "version": "v2.3.0", "source": { "type": "git", "url": "https://github.com/serversideup/spin.git", - "reference": "03bb69dbdc6d6a68b82b4bb4cfeb7accc4f8758f" + "reference": "e7f742dfe54146196da26876670f368c11852df3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/serversideup/spin/zipball/03bb69dbdc6d6a68b82b4bb4cfeb7accc4f8758f", - "reference": "03bb69dbdc6d6a68b82b4bb4cfeb7accc4f8758f", + "url": "https://api.github.com/repos/serversideup/spin/zipball/e7f742dfe54146196da26876670f368c11852df3", + "reference": "e7f742dfe54146196da26876670f368c11852df3", "shasum": "" }, "bin": [ @@ -14645,7 +14670,7 @@ "description": "Replicate your production environment locally using Docker. Just run \"spin up\". It's really that easy.", "support": { "issues": "https://github.com/serversideup/spin/issues", - "source": "https://github.com/serversideup/spin/tree/v1.1.0" + "source": "https://github.com/serversideup/spin/tree/v2.3.0" }, "funding": [ { @@ -14653,7 +14678,7 @@ "type": "github" } ], - "time": "2022-05-20T15:13:10+00:00" + "time": "2024-10-15T15:12:28+00:00" }, { "name": "spatie/error-solutions", @@ -14974,20 +14999,20 @@ }, { "name": "symfony/http-client", - "version": "v6.4.12", + "version": "v7.1.8", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56" + "reference": "c30d91a1deac0dc3ed5e604683cf2e1dfc635b8a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56", - "reference": "fbebfcce21084d3e91ea987ae5bdd8c71ff0fd56", + "url": "https://api.github.com/repos/symfony/http-client/zipball/c30d91a1deac0dc3ed5e604683cf2e1dfc635b8a", + "reference": "c30d91a1deac0dc3ed5e604683cf2e1dfc635b8a", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "psr/log": "^1|^2|^3", "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "^3.4.1", @@ -14995,7 +15020,7 @@ }, "conflict": { "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.3" + "symfony/http-foundation": "<6.4" }, "provide": { "php-http/async-client-implementation": "*", @@ -15012,11 +15037,12 @@ "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/http-kernel": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0" + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/http-kernel": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/rate-limiter": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -15047,7 +15073,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v6.4.12" + "source": "https://github.com/symfony/http-client/tree/v7.1.8" }, "funding": [ { @@ -15063,7 +15089,7 @@ "type": "tidelift" } ], - "time": "2024-09-20T08:21:33+00:00" + "time": "2024-11-13T13:40:27+00:00" }, { "name": "symfony/http-client-contracts", diff --git a/config/constants.php b/config/constants.php index e979b2087..c947635be 100644 --- a/config/constants.php +++ b/config/constants.php @@ -1,11 +1,47 @@ '26.0', - 'docs' => [ - 'base_url' => 'https://coolify.io/docs', + 'coolify' => [ + 'version' => '4.0.0-beta.374', + 'self_hosted' => env('SELF_HOSTED', true), + 'autoupdate' => env('AUTOUPDATE'), + 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), + 'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper'), + 'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false), + ], + + 'urls' => [ + 'docs' => 'https://coolify.io/docs', 'contact' => 'https://coolify.io/docs/contact', ], + + 'services' => [ + // Temporary disabled until cache is implemented + // 'official' => 'https://cdn.coollabs.io/coolify/service-templates.json', + 'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/main/templates/service-templates.json', + ], + + 'terminal' => [ + 'protocol' => env('TERMINAL_PROTOCOL'), + 'host' => env('TERMINAL_HOST'), + 'port' => env('TERMINAL_PORT'), + ], + + 'pusher' => [ + 'host' => env('PUSHER_HOST'), + 'port' => env('PUSHER_PORT'), + 'app_key' => env('PUSHER_APP_KEY'), + ], + + 'horizon' => [ + 'is_horizon_enabled' => env('HORIZON_ENABLED', true), + 'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true), + ], + + 'docker' => [ + 'minimum_required_version' => '26.0', + ], + 'ssh' => [ 'mux_enabled' => env('MUX_ENABLED', env('SSH_MUX_ENABLED', true)), 'mux_persist_time' => env('SSH_MUX_PERSIST_TIME', 3600), @@ -13,20 +49,14 @@ return [ 'server_interval' => 20, 'command_timeout' => 7200, ], - 'waitlist' => [ - 'expiration' => 10, - ], + 'invitation' => [ 'link' => [ 'base_url' => '/invitations/', - 'expiration' => 10, + 'expiration_days' => 3, ], ], - 'services' => [ - // Temporary disabled until cache is implemented - // 'official' => 'https://cdn.coollabs.io/coolify/service-templates.json', - 'official' => 'https://raw.githubusercontent.com/coollabsio/coolify/main/templates/service-templates.json', - ], + 'limits' => [ 'trial_period' => 0, 'server' => [ @@ -46,4 +76,23 @@ return [ 'dynamic' => true, ], ], + + 'waitlist' => [ + 'enabled' => env('WAITLIST', false), + 'expiration' => 10, + ], + + 'sentry' => [ + 'sentry_dsn' => env('SENTRY_DSN'), + ], + + 'webhooks' => [ + 'feedback_discord_webhook' => env('FEEDBACK_DISCORD_WEBHOOK'), + 'dev_webhook' => env('SERVEO_URL'), + ], + + 'bunny' => [ + 'storage_api_key' => env('BUNNY_STORAGE_API_KEY'), + 'api_key' => env('BUNNY_API_KEY'), + ], ]; diff --git a/config/coolify.php b/config/coolify.php deleted file mode 100644 index f9878fff7..000000000 --- a/config/coolify.php +++ /dev/null @@ -1,16 +0,0 @@ - 'https://coolify.io/docs/', - 'contact' => 'https://coolify.io/docs/contact', - 'feedback_discord_webhook' => env('FEEDBACK_DISCORD_WEBHOOK'), - 'self_hosted' => env('SELF_HOSTED', true), - 'waitlist' => env('WAITLIST', false), - 'license_url' => 'https://licenses.coollabs.io', - 'dev_webhook' => env('SERVEO_URL'), - 'is_windows_docker_desktop' => env('IS_WINDOWS_DOCKER_DESKTOP', false), - 'base_config_path' => env('BASE_CONFIG_PATH', '/data/coolify'), - 'helper_image' => env('HELPER_IMAGE', 'ghcr.io/coollabsio/coolify-helper'), - 'is_horizon_enabled' => env('HORIZON_ENABLED', true), - 'is_scheduler_enabled' => env('SCHEDULER_ENABLED', true), -]; diff --git a/config/redoc.php b/config/redoc.php new file mode 100644 index 000000000..439e93e7b --- /dev/null +++ b/config/redoc.php @@ -0,0 +1,28 @@ + '', + + /* + |-------------------------------------------------------------------------- + | Variables + |-------------------------------------------------------------------------- + | + | You can automatically replace variables in your OpenAPI definitions by + | adding a key value pair to the array below. This will replace any + | instances of :key with the given value. + | + */ + + 'variables' => [], + +]; diff --git a/config/sentry.php b/config/sentry.php index e8b6ab098..0efb4a0e2 100644 --- a/config/sentry.php +++ b/config/sentry.php @@ -3,11 +3,11 @@ return [ // @see https://docs.sentry.io/product/sentry-basics/dsn-explainer/ - 'dsn' => 'https://89552af6db48f4ca6a871ec0fc42964d@o1082494.ingest.us.sentry.io/4505347448045568', + 'dsn' => config('constants.sentry.sentry_dsn'), // The release version of your application // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) - 'release' => '4.0.0-beta.361', + 'release' => config('constants.coolify.version'), // When left empty or `null` the Laravel environment will be used 'environment' => config('app.env'), diff --git a/config/subscription.php b/config/subscription.php index 3e0182de9..a033862d2 100644 --- a/config/subscription.php +++ b/config/subscription.php @@ -6,21 +6,7 @@ return [ // Stripe 'stripe_api_key' => env('STRIPE_API_KEY', null), 'stripe_webhook_secret' => env('STRIPE_WEBHOOK_SECRET', null), - 'stripe_price_id_basic_monthly' => env('STRIPE_PRICE_ID_BASIC_MONTHLY', null), - 'stripe_price_id_basic_yearly' => env('STRIPE_PRICE_ID_BASIC_YEARLY', null), - 'stripe_price_id_pro_monthly' => env('STRIPE_PRICE_ID_PRO_MONTHLY', null), - 'stripe_price_id_pro_yearly' => env('STRIPE_PRICE_ID_PRO_YEARLY', null), - 'stripe_price_id_ultimate_monthly' => env('STRIPE_PRICE_ID_ULTIMATE_MONTHLY', null), - 'stripe_price_id_ultimate_yearly' => env('STRIPE_PRICE_ID_ULTIMATE_YEARLY', null), 'stripe_excluded_plans' => env('STRIPE_EXCLUDED_PLANS', null), - - 'stripe_price_id_basic_monthly_old' => env('STRIPE_PRICE_ID_BASIC_MONTHLY_OLD', null), - 'stripe_price_id_basic_yearly_old' => env('STRIPE_PRICE_ID_BASIC_YEARLY_OLD', null), - 'stripe_price_id_pro_monthly_old' => env('STRIPE_PRICE_ID_PRO_MONTHLY_OLD', null), - 'stripe_price_id_pro_yearly_old' => env('STRIPE_PRICE_ID_PRO_YEARLY_OLD', null), - 'stripe_price_id_ultimate_monthly_old' => env('STRIPE_PRICE_ID_ULTIMATE_MONTHLY_OLD', null), - 'stripe_price_id_ultimate_yearly_old' => env('STRIPE_PRICE_ID_ULTIMATE_YEARLY_OLD', null), - 'stripe_price_id_dynamic_monthly' => env('STRIPE_PRICE_ID_DYNAMIC_MONTHLY', null), 'stripe_price_id_dynamic_yearly' => env('STRIPE_PRICE_ID_DYNAMIC_YEARLY', null), ]; diff --git a/config/version.php b/config/version.php deleted file mode 100644 index 0e83ff40e..000000000 --- a/config/version.php +++ /dev/null @@ -1,3 +0,0 @@ -longText('custom_nginx_configuration')->nullable()->after('static_image'); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('applications', function (Blueprint $table) { + $table->dropColumn('custom_nginx_configuration'); + }); + } +}; diff --git a/database/migrations/2024_11_11_125366_add_index_to_activity_log.php b/database/migrations/2024_11_11_125366_add_index_to_activity_log.php new file mode 100644 index 000000000..0c281ff40 --- /dev/null +++ b/database/migrations/2024_11_11_125366_add_index_to_activity_log.php @@ -0,0 +1,28 @@ +getMessage()); + } + } + + public function down() + { + try { + DB::statement('DROP INDEX IF EXISTS idx_activity_type_uuid'); + DB::statement('ALTER TABLE activity_log ALTER COLUMN properties TYPE json USING properties::json'); + } catch (\Exception $e) { + Log::error('Error dropping index from activity_log: '.$e->getMessage()); + } + } +} diff --git a/database/seeders/PopulateSshKeysDirectorySeeder.php b/database/seeders/PopulateSshKeysDirectorySeeder.php index d528179c0..87984769c 100644 --- a/database/seeders/PopulateSshKeysDirectorySeeder.php +++ b/database/seeders/PopulateSshKeysDirectorySeeder.php @@ -24,9 +24,8 @@ class PopulateSshKeysDirectorySeeder extends Seeder }); if (isDev()) { - $user = env('PUID').':'.env('PGID'); - Process::run("chown -R $user ".storage_path('app/ssh/keys')); - Process::run("chown -R $user ".storage_path('app/ssh/mux')); + Process::run('chown -R 9999:9999 '.storage_path('app/ssh/keys')); + Process::run('chown -R 9999:9999 '.storage_path('app/ssh/mux')); } else { Process::run('chown -R 9999:root '.storage_path('app/ssh/keys')); Process::run('chown -R 9999:root '.storage_path('app/ssh/mux')); diff --git a/database/seeders/ProductionSeeder.php b/database/seeders/ProductionSeeder.php index 3e820a162..7acdef548 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 "Running in cloud mode.\n"; + echo "[x]: Running in cloud mode.\n"; } else { - echo "Running in self-hosted mode.\n"; + echo "[x]: Running in self-hosted mode.\n"; } // Fix for 4.0.0-beta.37 @@ -100,7 +100,7 @@ class ProductionSeeder extends Seeder } } - if (! isCloud() && config('coolify.is_windows_docker_desktop') == false) { + if (! isCloud() && config('constants.coolify.is_windows_docker_desktop') == false) { $coolify_key_name = '@host.docker.internal'; $ssh_keys_directory = Storage::disk('ssh-keys')->files(); $coolify_key = collect($ssh_keys_directory)->firstWhere(fn ($item) => str($item)->contains($coolify_key_name)); @@ -127,7 +127,7 @@ class ProductionSeeder extends Seeder } } } - if (config('coolify.is_windows_docker_desktop')) { + if (config('constants.coolify.is_windows_docker_desktop')) { PrivateKey::updateOrCreate( [ 'id' => 0, diff --git a/database/seeders/SentinelSeeder.php b/database/seeders/SentinelSeeder.php index d61c2f55d..3cf913933 100644 --- a/database/seeders/SentinelSeeder.php +++ b/database/seeders/SentinelSeeder.php @@ -14,10 +14,10 @@ class SentinelSeeder extends Seeder foreach ($servers as $server) { try { if (str($server->settings->sentinel_token)->isEmpty()) { - $server->settings->generateSentinelToken(); + $server->settings->generateSentinelToken(ignoreEvent: true); } if (str($server->settings->sentinel_custom_url)->isEmpty()) { - $url = $server->settings->generateSentinelUrl(); + $url = $server->settings->generateSentinelUrl(ignoreEvent: true); if (str($url)->isEmpty()) { $server->settings->is_sentinel_enabled = false; $server->settings->save(); diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index f1da567fc..05b9f9cfb 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -60,7 +60,7 @@ services: SOKETI_DEFAULT_APP_SECRET: "${PUSHER_APP_SECRET:-coolify}" entrypoint: ["/bin/sh", "/soketi-entrypoint.sh"] vite: - image: node:20 + image: node:20-alpine pull_policy: always working_dir: /var/www/html environment: @@ -74,8 +74,9 @@ services: networks: - coolify testing-host: - image: "ghcr.io/coollabsio/coolify-testing-host:latest" - pull_policy: always + build: + context: . + dockerfile: ./docker/testing-host/Dockerfile init: true container_name: coolify-testing-host volumes: @@ -88,7 +89,7 @@ services: networks: - coolify mailpit: - image: "axllent/mailpit:latest" + image: axllent/mailpit:latest pull_policy: always container_name: coolify-mail ports: diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index b15a109c3..d86b2336b 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -29,6 +29,7 @@ services: - REDIS_HOST - REDIS_PASSWORD - HORIZON_BALANCE + - HORIZON_MIN_PROCESSES - HORIZON_MAX_PROCESSES - HORIZON_BALANCE_MAX_SHIFT - HORIZON_BALANCE_COOLDOWN @@ -50,29 +51,8 @@ services: - TERMINAL_HOST - TERMINAL_PORT - AUTOUPDATE - - SELF_HOSTED - SSH_MUX_ENABLED - SSH_MUX_PERSIST_TIME - - FEEDBACK_DISCORD_WEBHOOK - - WAITLIST - - SUBSCRIPTION_PROVIDER - - STRIPE_API_KEY - - STRIPE_WEBHOOK_SECRET - - STRIPE_PRICE_ID_BASIC_MONTHLY - - STRIPE_PRICE_ID_BASIC_YEARLY - - STRIPE_PRICE_ID_PRO_MONTHLY - - STRIPE_PRICE_ID_PRO_YEARLY - - STRIPE_PRICE_ID_ULTIMATE_MONTHLY - - STRIPE_PRICE_ID_ULTIMATE_YEARLY - - STRIPE_PRICE_ID_DYNAMIC_MONTHLY - - STRIPE_PRICE_ID_DYNAMIC_YEARLY - - STRIPE_PRICE_ID_BASIC_MONTHLY_OLD - - STRIPE_PRICE_ID_BASIC_YEARLY_OLD - - STRIPE_PRICE_ID_PRO_MONTHLY_OLD - - STRIPE_PRICE_ID_PRO_YEARLY_OLD - - STRIPE_PRICE_ID_ULTIMATE_MONTHLY_OLD - - STRIPE_PRICE_ID_ULTIMATE_YEARLY_OLD - - STRIPE_EXCLUDED_PLANS ports: - "${APP_PORT:-8000}:80" expose: @@ -113,7 +93,7 @@ services: retries: 10 timeout: 2s soketi: - image: 'ghcr.io/coollabsio/coolify-realtime:1.0.3' + image: 'ghcr.io/coollabsio/coolify-realtime:1.0.4' ports: - "${SOKETI_PORT:-6001}:6001" - "6002:6002" diff --git a/docker-compose.windows.yml b/docker-compose.windows.yml index ef2de82e9..f2b03d209 100644 --- a/docker-compose.windows.yml +++ b/docker-compose.windows.yml @@ -86,7 +86,7 @@ services: retries: 10 timeout: 2s redis: - image: redis:alpine + image: redis:7-alpine pull_policy: always container_name: coolify-redis restart: always @@ -103,7 +103,7 @@ services: retries: 10 timeout: 2s soketi: - image: 'ghcr.io/coollabsio/coolify-realtime:1.0.0' + image: 'ghcr.io/coollabsio/coolify-realtime:1.0.4' pull_policy: always container_name: coolify-realtime restart: always diff --git a/docker-compose.yml b/docker-compose.yml index 68d0f0744..0fd3dda07 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,7 +4,7 @@ services: restart: always working_dir: /var/www/html extra_hosts: - - 'host.docker.internal:host-gateway' + - host.docker.internal:host-gateway networks: - coolify depends_on: @@ -18,7 +18,7 @@ services: networks: - coolify redis: - image: redis:alpine + image: redis:7-alpine container_name: coolify-redis restart: always networks: @@ -26,7 +26,7 @@ services: soketi: container_name: coolify-realtime extra_hosts: - - 'host.docker.internal:host-gateway' + - host.docker.internal:host-gateway restart: always networks: - coolify diff --git a/docker/coolify-helper/Dockerfile b/docker/coolify-helper/Dockerfile index 7aa9d8722..741fff764 100644 --- a/docker/coolify-helper/Dockerfile +++ b/docker/coolify-helper/Dockerfile @@ -1,16 +1,30 @@ -FROM alpine:3.17 +# Versions -ARG TARGETPLATFORM +# https://hub.docker.com/_/alpine +ARG BASE_IMAGE=alpine:3.20 # https://download.docker.com/linux/static/stable/ -ARG DOCKER_VERSION=26.1.3 +ARG DOCKER_VERSION=27.3.1 # https://github.com/docker/compose/releases -ARG DOCKER_COMPOSE_VERSION=2.27.1 +ARG DOCKER_COMPOSE_VERSION=2.30.3 # https://github.com/docker/buildx/releases -ARG DOCKER_BUILDX_VERSION=0.14.1 +ARG DOCKER_BUILDX_VERSION=0.18.0 # https://github.com/buildpacks/pack/releases ARG PACK_VERSION=0.35.1 # https://github.com/railwayapp/nixpacks/releases -ARG NIXPACKS_VERSION=1.28.0 +ARG NIXPACKS_VERSION=1.29.0 +# https://hub.docker.com/r/minio/mc/tags +ARG MINIO_VERSION=RELEASE.2024-03-07T00-31-49Z + +FROM minio/mc:${MINIO_VERSION} AS minio-client + +FROM ${BASE_IMAGE} AS base + +ARG TARGETPLATFORM +ARG DOCKER_VERSION +ARG DOCKER_COMPOSE_VERSION +ARG DOCKER_BUILDX_VERSION +ARG PACK_VERSION +ARG NIXPACKS_VERSION USER root WORKDIR /artifacts @@ -34,9 +48,8 @@ RUN if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \ chmod +x ~/.docker/cli-plugins/docker-compose /usr/bin/docker /usr/local/bin/pack /root/.docker/cli-plugins/docker-buildx \ ;fi -COPY --from=minio/mc:RELEASE.2024-09-09T07-53-10Z /usr/bin/mc /usr/bin/mc +COPY --from=minio-client /usr/bin/mc /usr/bin/mc RUN chmod +x /usr/bin/mc ENTRYPOINT ["/sbin/tini", "--"] CMD ["tail", "-f", "/dev/null"] - diff --git a/docker/coolify-realtime/Dockerfile b/docker/coolify-realtime/Dockerfile index f0d6db906..b9f3c1e62 100644 --- a/docker/coolify-realtime/Dockerfile +++ b/docker/coolify-realtime/Dockerfile @@ -1,5 +1,6 @@ FROM quay.io/soketi/soketi:1.6-16-alpine + ARG TARGETPLATFORM # https://github.com/cloudflare/cloudflared/releases ARG CLOUDFLARED_VERSION=2024.4.1 @@ -22,6 +23,4 @@ RUN /bin/sh -c "if [[ ${TARGETPLATFORM} == 'linux/arm64' ]]; then \ 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" - - ENTRYPOINT ["/bin/sh", "/soketi-entrypoint.sh"] diff --git a/docker/coolify-realtime/package-lock.json b/docker/coolify-realtime/package-lock.json new file mode 100644 index 000000000..9316442ae --- /dev/null +++ b/docker/coolify-realtime/package-lock.json @@ -0,0 +1,190 @@ +{ + "name": "coolify-realtime", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@xterm/addon-fit": "0.10.0", + "@xterm/xterm": "5.5.0", + "axios": "1.7.7", + "cookie": "1.0.1", + "dotenv": "16.4.5", + "node-pty": "1.0.0", + "ws": "8.18.0" + } + }, + "node_modules/@xterm/addon-fit": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/@xterm/addon-fit/-/addon-fit-0.10.0.tgz", + "integrity": "sha512-UFYkDm4HUahf2lnEyHvio51TNGiLK66mqP2JoATy7hRZeXaGMRDr00JiSF7m63vR5WKATF605yEggJKsw0JpMQ==", + "license": "MIT", + "peerDependencies": { + "@xterm/xterm": "^5.0.0" + } + }, + "node_modules/@xterm/xterm": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-5.5.0.tgz", + "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cookie": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.1.tgz", + "integrity": "sha512-Xd8lFX4LM9QEEwxQpF9J9NTUh8pmdJO0cyRJhFiDoLTk2eH8FXlRv2IFGYVadZpqI3j8fhNrSdKCeYPxiAhLXw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/nan": { + "version": "2.22.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.22.0.tgz", + "integrity": "sha512-nbajikzWTMwsW+eSsNm3QwlOs7het9gGJU5dDZzRTQGk03vyBOauxgI4VakDzE0PtsGTmXPsXTbbjVhRwR5mpw==", + "license": "MIT" + }, + "node_modules/node-pty": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz", + "integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "nan": "^2.17.0" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + } + } +} diff --git a/docker/coolify-realtime/package.json b/docker/coolify-realtime/package.json index 146e6b90a..8b2076f8d 100644 --- a/docker/coolify-realtime/package.json +++ b/docker/coolify-realtime/package.json @@ -2,12 +2,12 @@ "private": true, "type": "module", "dependencies": { - "@xterm/addon-fit": "^0.10.0", - "@xterm/xterm": "^5.5.0", - "cookie": "^0.7.0", - "axios": "1.7.5", - "dotenv": "^16.4.5", - "node-pty": "^1.0.0", - "ws": "^8.17.0" + "@xterm/addon-fit": "0.10.0", + "@xterm/xterm": "5.5.0", + "cookie": "1.0.1", + "axios": "1.7.7", + "dotenv": "16.4.5", + "node-pty": "1.0.0", + "ws": "8.18.0" } } diff --git a/docker/dev/Dockerfile b/docker/dev/Dockerfile index d2381f764..e90521759 100644 --- a/docker/dev/Dockerfile +++ b/docker/dev/Dockerfile @@ -1,10 +1,21 @@ -FROM serversideup/php:8.2-fpm-nginx-v2.2.1 +# 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 -# https://github.com/cloudflare/cloudflared/releases -ARG CLOUDFLARED_VERSION=2024.4.1 - -ARG POSTGRES_VERSION=15 +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" @@ -41,7 +52,7 @@ RUN --mount=type=cache,target=/root/.cache \ 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/mc:RELEASE.2024-09-09T07-53-10Z /usr/bin/mc /usr/bin/mc +COPY --from=minio-client /usr/bin/mc /usr/bin/mc RUN chmod +x /usr/bin/mc RUN { \ diff --git a/docker/prod/Dockerfile b/docker/prod/Dockerfile index 36ca5ebb3..36be6cf06 100644 --- a/docker/prod/Dockerfile +++ b/docker/prod/Dockerfile @@ -1,22 +1,34 @@ -FROM serversideup/php:8.2-fpm-nginx-v2.2.1 AS base +# 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 +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 serversideup/php:8.2-fpm-nginx-v2.2.1 +FROM minio/mc:${MINIO_VERSION} AS minio-client + +FROM serversideup/php:${SERVERSIDEUP_PHP_VERSION} ARG TARGETPLATFORM -# https://github.com/cloudflare/cloudflared/releases -ARG CLOUDFLARED_VERSION=2024.4.1 -ARG POSTGRES_VERSION=15 +ARG CLOUDFLARED_VERSION +ARG POSTGRES_VERSION ARG CI=true WORKDIR /var/www/html @@ -29,7 +41,7 @@ RUN curl -fSsL https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmo 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 +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 @@ -45,11 +57,6 @@ 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 php artisan route:clear -RUN php artisan view:clear -RUN php artisan route:cache -RUN php artisan view:cache - 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 @@ -71,5 +78,5 @@ RUN { \ echo 'post_max_size=256M'; \ } > /etc/php/current_version/cli/conf.d/upload-limits.ini -COPY --from=minio/mc:RELEASE.2024-09-09T07-53-10Z /usr/bin/mc /usr/bin/mc +COPY --from=minio-client /usr/bin/mc /usr/bin/mc RUN chmod +x /usr/bin/mc diff --git a/docker/testing-host/Dockerfile b/docker/testing-host/Dockerfile index deb09eeba..d98fcc821 100644 --- a/docker/testing-host/Dockerfile +++ b/docker/testing-host/Dockerfile @@ -1,16 +1,22 @@ +# Versions +# https://download.docker.com/linux/static/stable/ +ARG DOCKER_VERSION=27.3.1 +# https://github.com/docker/compose/releases +ARG DOCKER_COMPOSE_VERSION=2.30.3 +# https://github.com/docker/buildx/releases +ARG DOCKER_BUILDX_VERSION=0.18.0 + + FROM debian:12-slim ARG TARGETPLATFORM -# https://download.docker.com/linux/static/stable/ -ARG DOCKER_VERSION=26.1.2 -# https://github.com/docker/compose/releases -ARG DOCKER_COMPOSE_VERSION=2.27.0 -# https://github.com/docker/buildx/releases -ARG DOCKER_BUILDX_VERSION=0.14.0 +ARG DOCKER_VERSION +ARG DOCKER_COMPOSE_VERSION +ARG DOCKER_BUILDX_VERSION USER root WORKDIR /root -ENV PATH "$PATH:/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin" +ENV PATH="/host/usr/local/sbin:/host/usr/local/bin:/host/usr/sbin:/host/usr/bin:/host/sbin:/host/bin:$PATH" RUN apt update && apt -y install openssh-client openssh-server curl wget git jq jc RUN mkdir -p ~/.docker/cli-plugins diff --git a/examples/traefik-dynamic-catch-all.yaml b/examples/traefik-dynamic-catch-all.yaml deleted file mode 100644 index 54f7b1fb9..000000000 --- a/examples/traefik-dynamic-catch-all.yaml +++ /dev/null @@ -1,23 +0,0 @@ -# This is an example dynamic configuration. -http: - routers: - catchall: - entryPoints: - - http - - https - service: noop - rule: HostRegexp(`{catchall:.*}`) - priority: 1 - middlewares: - - redirect-regexp - services: - noop: - loadBalancer: - servers: - - url: '' - middlewares: - redirect-regexp: - redirectRegex: - regex: '(.*)' - replacement: 'https://coolify.io' - permanent: false \ No newline at end of file diff --git a/examples/traefik-dynamic-coolify.yaml b/examples/traefik-dynamic-coolify.yaml deleted file mode 100644 index 0c5f7e311..000000000 --- a/examples/traefik-dynamic-coolify.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# This is an example dynamic configuration. -http: - routers: - coolify-http: - entryPoints: - - http - service: coolify - rule: Host(`coolify.io`) - services: - coolify: - loadBalancer: - servers: - - - url: 'http://coolify:80' \ No newline at end of file diff --git a/openapi.json b/openapi.json new file mode 100644 index 000000000..2ec218438 --- /dev/null +++ b/openapi.json @@ -0,0 +1,7996 @@ +{ + "openapi": "3.1.0", + "info": { + "title": "Coolify", + "version": "0.1" + }, + "servers": [ + { + "url": "https:\/\/app.coolify.io\/api\/v1", + "description": "Coolify Cloud API. Change the host to your own instance if you are self-hosting." + } + ], + "paths": { + "\/applications": { + "get": { + "tags": [ + "Applications" + ], + "summary": "List", + "description": "List all applications.", + "operationId": "list-applications", + "responses": { + "200": { + "description": "Get all applications.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/Application" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/public": { + "post": { + "tags": [ + "Applications" + ], + "summary": "Create (Public)", + "description": "Create new application based on a public git repository.", + "operationId": "create-public-application", + "requestBody": { + "description": "Application object that needs to be created.", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "project_uuid", + "server_uuid", + "environment_name", + "git_repository", + "git_branch", + "build_pack", + "ports_exposes" + ], + "properties": { + "project_uuid": { + "type": "string", + "description": "The project UUID." + }, + "server_uuid": { + "type": "string", + "description": "The server UUID." + }, + "environment_name": { + "type": "string", + "description": "The environment name." + }, + "git_repository": { + "type": "string", + "description": "The git repository URL." + }, + "git_branch": { + "type": "string", + "description": "The git branch." + }, + "build_pack": { + "type": "string", + "enum": [ + "nixpacks", + "static", + "dockerfile", + "dockercompose" + ], + "description": "The build pack type." + }, + "ports_exposes": { + "type": "string", + "description": "The ports to expose." + }, + "destination_uuid": { + "type": "string", + "description": "The destination UUID." + }, + "name": { + "type": "string", + "description": "The application name." + }, + "description": { + "type": "string", + "description": "The application description." + }, + "domains": { + "type": "string", + "description": "The application domains." + }, + "git_commit_sha": { + "type": "string", + "description": "The git commit SHA." + }, + "docker_registry_image_name": { + "type": "string", + "description": "The docker registry image name." + }, + "docker_registry_image_tag": { + "type": "string", + "description": "The docker registry image tag." + }, + "is_static": { + "type": "boolean", + "description": "The flag to indicate if the application is static." + }, + "static_image": { + "type": "string", + "enum": [ + "nginx:alpine" + ], + "description": "The static image." + }, + "install_command": { + "type": "string", + "description": "The install command." + }, + "build_command": { + "type": "string", + "description": "The build command." + }, + "start_command": { + "type": "string", + "description": "The start command." + }, + "ports_mappings": { + "type": "string", + "description": "The ports mappings." + }, + "base_directory": { + "type": "string", + "description": "The base directory for all commands." + }, + "publish_directory": { + "type": "string", + "description": "The publish directory." + }, + "health_check_enabled": { + "type": "boolean", + "description": "Health check enabled." + }, + "health_check_path": { + "type": "string", + "description": "Health check path." + }, + "health_check_port": { + "type": "string", + "nullable": true, + "description": "Health check port." + }, + "health_check_host": { + "type": "string", + "nullable": true, + "description": "Health check host." + }, + "health_check_method": { + "type": "string", + "description": "Health check method." + }, + "health_check_return_code": { + "type": "integer", + "description": "Health check return code." + }, + "health_check_scheme": { + "type": "string", + "description": "Health check scheme." + }, + "health_check_response_text": { + "type": "string", + "nullable": true, + "description": "Health check response text." + }, + "health_check_interval": { + "type": "integer", + "description": "Health check interval in seconds." + }, + "health_check_timeout": { + "type": "integer", + "description": "Health check timeout in seconds." + }, + "health_check_retries": { + "type": "integer", + "description": "Health check retries count." + }, + "health_check_start_period": { + "type": "integer", + "description": "Health check start period in seconds." + }, + "limits_memory": { + "type": "string", + "description": "Memory limit." + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit." + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness." + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation." + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit." + }, + "limits_cpuset": { + "type": "string", + "nullable": true, + "description": "CPU set." + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares." + }, + "custom_labels": { + "type": "string", + "description": "Custom labels." + }, + "custom_docker_run_options": { + "type": "string", + "description": "Custom docker run options." + }, + "post_deployment_command": { + "type": "string", + "description": "Post deployment command." + }, + "post_deployment_command_container": { + "type": "string", + "description": "Post deployment command container." + }, + "pre_deployment_command": { + "type": "string", + "description": "Pre deployment command." + }, + "pre_deployment_command_container": { + "type": "string", + "description": "Pre deployment command container." + }, + "manual_webhook_secret_github": { + "type": "string", + "description": "Manual webhook secret for Github." + }, + "manual_webhook_secret_gitlab": { + "type": "string", + "description": "Manual webhook secret for Gitlab." + }, + "manual_webhook_secret_bitbucket": { + "type": "string", + "description": "Manual webhook secret for Bitbucket." + }, + "manual_webhook_secret_gitea": { + "type": "string", + "description": "Manual webhook secret for Gitea." + }, + "redirect": { + "type": "string", + "nullable": true, + "description": "How to set redirect with Traefik \/ Caddy. www<->non-www.", + "enum": [ + "www", + "non-www", + "both" + ] + }, + "instant_deploy": { + "type": "boolean", + "description": "The flag to indicate if the application should be deployed instantly." + }, + "dockerfile": { + "type": "string", + "description": "The Dockerfile content." + }, + "docker_compose_location": { + "type": "string", + "description": "The Docker Compose location." + }, + "docker_compose_raw": { + "type": "string", + "description": "The Docker Compose raw content." + }, + "docker_compose_custom_start_command": { + "type": "string", + "description": "The Docker Compose custom start command." + }, + "docker_compose_custom_build_command": { + "type": "string", + "description": "The Docker Compose custom build command." + }, + "docker_compose_domains": { + "type": "array", + "description": "The Docker Compose domains." + }, + "watch_paths": { + "type": "string", + "description": "The watch paths." + }, + "use_build_server": { + "type": "boolean", + "nullable": true, + "description": "Use build server." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Application created successfully." + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/private-github-app": { + "post": { + "tags": [ + "Applications" + ], + "summary": "Create (Private - GH App)", + "description": "Create new application based on a private repository through a Github App.", + "operationId": "create-private-github-app-application", + "requestBody": { + "description": "Application object that needs to be created.", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "project_uuid", + "server_uuid", + "environment_name", + "github_app_uuid", + "git_repository", + "git_branch", + "build_pack", + "ports_exposes" + ], + "properties": { + "project_uuid": { + "type": "string", + "description": "The project UUID." + }, + "server_uuid": { + "type": "string", + "description": "The server UUID." + }, + "environment_name": { + "type": "string", + "description": "The environment name." + }, + "github_app_uuid": { + "type": "string", + "description": "The Github App UUID." + }, + "git_repository": { + "type": "string", + "description": "The git repository URL." + }, + "git_branch": { + "type": "string", + "description": "The git branch." + }, + "ports_exposes": { + "type": "string", + "description": "The ports to expose." + }, + "destination_uuid": { + "type": "string", + "description": "The destination UUID." + }, + "build_pack": { + "type": "string", + "enum": [ + "nixpacks", + "static", + "dockerfile", + "dockercompose" + ], + "description": "The build pack type." + }, + "name": { + "type": "string", + "description": "The application name." + }, + "description": { + "type": "string", + "description": "The application description." + }, + "domains": { + "type": "string", + "description": "The application domains." + }, + "git_commit_sha": { + "type": "string", + "description": "The git commit SHA." + }, + "docker_registry_image_name": { + "type": "string", + "description": "The docker registry image name." + }, + "docker_registry_image_tag": { + "type": "string", + "description": "The docker registry image tag." + }, + "is_static": { + "type": "boolean", + "description": "The flag to indicate if the application is static." + }, + "static_image": { + "type": "string", + "enum": [ + "nginx:alpine" + ], + "description": "The static image." + }, + "install_command": { + "type": "string", + "description": "The install command." + }, + "build_command": { + "type": "string", + "description": "The build command." + }, + "start_command": { + "type": "string", + "description": "The start command." + }, + "ports_mappings": { + "type": "string", + "description": "The ports mappings." + }, + "base_directory": { + "type": "string", + "description": "The base directory for all commands." + }, + "publish_directory": { + "type": "string", + "description": "The publish directory." + }, + "health_check_enabled": { + "type": "boolean", + "description": "Health check enabled." + }, + "health_check_path": { + "type": "string", + "description": "Health check path." + }, + "health_check_port": { + "type": "string", + "nullable": true, + "description": "Health check port." + }, + "health_check_host": { + "type": "string", + "nullable": true, + "description": "Health check host." + }, + "health_check_method": { + "type": "string", + "description": "Health check method." + }, + "health_check_return_code": { + "type": "integer", + "description": "Health check return code." + }, + "health_check_scheme": { + "type": "string", + "description": "Health check scheme." + }, + "health_check_response_text": { + "type": "string", + "nullable": true, + "description": "Health check response text." + }, + "health_check_interval": { + "type": "integer", + "description": "Health check interval in seconds." + }, + "health_check_timeout": { + "type": "integer", + "description": "Health check timeout in seconds." + }, + "health_check_retries": { + "type": "integer", + "description": "Health check retries count." + }, + "health_check_start_period": { + "type": "integer", + "description": "Health check start period in seconds." + }, + "limits_memory": { + "type": "string", + "description": "Memory limit." + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit." + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness." + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation." + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit." + }, + "limits_cpuset": { + "type": "string", + "nullable": true, + "description": "CPU set." + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares." + }, + "custom_labels": { + "type": "string", + "description": "Custom labels." + }, + "custom_docker_run_options": { + "type": "string", + "description": "Custom docker run options." + }, + "post_deployment_command": { + "type": "string", + "description": "Post deployment command." + }, + "post_deployment_command_container": { + "type": "string", + "description": "Post deployment command container." + }, + "pre_deployment_command": { + "type": "string", + "description": "Pre deployment command." + }, + "pre_deployment_command_container": { + "type": "string", + "description": "Pre deployment command container." + }, + "manual_webhook_secret_github": { + "type": "string", + "description": "Manual webhook secret for Github." + }, + "manual_webhook_secret_gitlab": { + "type": "string", + "description": "Manual webhook secret for Gitlab." + }, + "manual_webhook_secret_bitbucket": { + "type": "string", + "description": "Manual webhook secret for Bitbucket." + }, + "manual_webhook_secret_gitea": { + "type": "string", + "description": "Manual webhook secret for Gitea." + }, + "redirect": { + "type": "string", + "nullable": true, + "description": "How to set redirect with Traefik \/ Caddy. www<->non-www.", + "enum": [ + "www", + "non-www", + "both" + ] + }, + "instant_deploy": { + "type": "boolean", + "description": "The flag to indicate if the application should be deployed instantly." + }, + "dockerfile": { + "type": "string", + "description": "The Dockerfile content." + }, + "docker_compose_location": { + "type": "string", + "description": "The Docker Compose location." + }, + "docker_compose_raw": { + "type": "string", + "description": "The Docker Compose raw content." + }, + "docker_compose_custom_start_command": { + "type": "string", + "description": "The Docker Compose custom start command." + }, + "docker_compose_custom_build_command": { + "type": "string", + "description": "The Docker Compose custom build command." + }, + "docker_compose_domains": { + "type": "array", + "description": "The Docker Compose domains." + }, + "watch_paths": { + "type": "string", + "description": "The watch paths." + }, + "use_build_server": { + "type": "boolean", + "nullable": true, + "description": "Use build server." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Application created successfully." + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/private-deploy-key": { + "post": { + "tags": [ + "Applications" + ], + "summary": "Create (Private - Deploy Key)", + "description": "Create new application based on a private repository through a Deploy Key.", + "operationId": "create-private-deploy-key-application", + "requestBody": { + "description": "Application object that needs to be created.", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "project_uuid", + "server_uuid", + "environment_name", + "private_key_uuid", + "git_repository", + "git_branch", + "build_pack", + "ports_exposes" + ], + "properties": { + "project_uuid": { + "type": "string", + "description": "The project UUID." + }, + "server_uuid": { + "type": "string", + "description": "The server UUID." + }, + "environment_name": { + "type": "string", + "description": "The environment name." + }, + "private_key_uuid": { + "type": "string", + "description": "The private key UUID." + }, + "git_repository": { + "type": "string", + "description": "The git repository URL." + }, + "git_branch": { + "type": "string", + "description": "The git branch." + }, + "ports_exposes": { + "type": "string", + "description": "The ports to expose." + }, + "destination_uuid": { + "type": "string", + "description": "The destination UUID." + }, + "build_pack": { + "type": "string", + "enum": [ + "nixpacks", + "static", + "dockerfile", + "dockercompose" + ], + "description": "The build pack type." + }, + "name": { + "type": "string", + "description": "The application name." + }, + "description": { + "type": "string", + "description": "The application description." + }, + "domains": { + "type": "string", + "description": "The application domains." + }, + "git_commit_sha": { + "type": "string", + "description": "The git commit SHA." + }, + "docker_registry_image_name": { + "type": "string", + "description": "The docker registry image name." + }, + "docker_registry_image_tag": { + "type": "string", + "description": "The docker registry image tag." + }, + "is_static": { + "type": "boolean", + "description": "The flag to indicate if the application is static." + }, + "static_image": { + "type": "string", + "enum": [ + "nginx:alpine" + ], + "description": "The static image." + }, + "install_command": { + "type": "string", + "description": "The install command." + }, + "build_command": { + "type": "string", + "description": "The build command." + }, + "start_command": { + "type": "string", + "description": "The start command." + }, + "ports_mappings": { + "type": "string", + "description": "The ports mappings." + }, + "base_directory": { + "type": "string", + "description": "The base directory for all commands." + }, + "publish_directory": { + "type": "string", + "description": "The publish directory." + }, + "health_check_enabled": { + "type": "boolean", + "description": "Health check enabled." + }, + "health_check_path": { + "type": "string", + "description": "Health check path." + }, + "health_check_port": { + "type": "string", + "nullable": true, + "description": "Health check port." + }, + "health_check_host": { + "type": "string", + "nullable": true, + "description": "Health check host." + }, + "health_check_method": { + "type": "string", + "description": "Health check method." + }, + "health_check_return_code": { + "type": "integer", + "description": "Health check return code." + }, + "health_check_scheme": { + "type": "string", + "description": "Health check scheme." + }, + "health_check_response_text": { + "type": "string", + "nullable": true, + "description": "Health check response text." + }, + "health_check_interval": { + "type": "integer", + "description": "Health check interval in seconds." + }, + "health_check_timeout": { + "type": "integer", + "description": "Health check timeout in seconds." + }, + "health_check_retries": { + "type": "integer", + "description": "Health check retries count." + }, + "health_check_start_period": { + "type": "integer", + "description": "Health check start period in seconds." + }, + "limits_memory": { + "type": "string", + "description": "Memory limit." + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit." + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness." + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation." + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit." + }, + "limits_cpuset": { + "type": "string", + "nullable": true, + "description": "CPU set." + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares." + }, + "custom_labels": { + "type": "string", + "description": "Custom labels." + }, + "custom_docker_run_options": { + "type": "string", + "description": "Custom docker run options." + }, + "post_deployment_command": { + "type": "string", + "description": "Post deployment command." + }, + "post_deployment_command_container": { + "type": "string", + "description": "Post deployment command container." + }, + "pre_deployment_command": { + "type": "string", + "description": "Pre deployment command." + }, + "pre_deployment_command_container": { + "type": "string", + "description": "Pre deployment command container." + }, + "manual_webhook_secret_github": { + "type": "string", + "description": "Manual webhook secret for Github." + }, + "manual_webhook_secret_gitlab": { + "type": "string", + "description": "Manual webhook secret for Gitlab." + }, + "manual_webhook_secret_bitbucket": { + "type": "string", + "description": "Manual webhook secret for Bitbucket." + }, + "manual_webhook_secret_gitea": { + "type": "string", + "description": "Manual webhook secret for Gitea." + }, + "redirect": { + "type": "string", + "nullable": true, + "description": "How to set redirect with Traefik \/ Caddy. www<->non-www.", + "enum": [ + "www", + "non-www", + "both" + ] + }, + "instant_deploy": { + "type": "boolean", + "description": "The flag to indicate if the application should be deployed instantly." + }, + "dockerfile": { + "type": "string", + "description": "The Dockerfile content." + }, + "docker_compose_location": { + "type": "string", + "description": "The Docker Compose location." + }, + "docker_compose_raw": { + "type": "string", + "description": "The Docker Compose raw content." + }, + "docker_compose_custom_start_command": { + "type": "string", + "description": "The Docker Compose custom start command." + }, + "docker_compose_custom_build_command": { + "type": "string", + "description": "The Docker Compose custom build command." + }, + "docker_compose_domains": { + "type": "array", + "description": "The Docker Compose domains." + }, + "watch_paths": { + "type": "string", + "description": "The watch paths." + }, + "use_build_server": { + "type": "boolean", + "nullable": true, + "description": "Use build server." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Application created successfully." + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/dockerfile": { + "post": { + "tags": [ + "Applications" + ], + "summary": "Create (Dockerfile)", + "description": "Create new application based on a simple Dockerfile.", + "operationId": "create-dockerfile-application", + "requestBody": { + "description": "Application object that needs to be created.", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "project_uuid", + "server_uuid", + "environment_name", + "dockerfile" + ], + "properties": { + "project_uuid": { + "type": "string", + "description": "The project UUID." + }, + "server_uuid": { + "type": "string", + "description": "The server UUID." + }, + "environment_name": { + "type": "string", + "description": "The environment name." + }, + "dockerfile": { + "type": "string", + "description": "The Dockerfile content." + }, + "build_pack": { + "type": "string", + "enum": [ + "nixpacks", + "static", + "dockerfile", + "dockercompose" + ], + "description": "The build pack type." + }, + "ports_exposes": { + "type": "string", + "description": "The ports to expose." + }, + "destination_uuid": { + "type": "string", + "description": "The destination UUID." + }, + "name": { + "type": "string", + "description": "The application name." + }, + "description": { + "type": "string", + "description": "The application description." + }, + "domains": { + "type": "string", + "description": "The application domains." + }, + "docker_registry_image_name": { + "type": "string", + "description": "The docker registry image name." + }, + "docker_registry_image_tag": { + "type": "string", + "description": "The docker registry image tag." + }, + "ports_mappings": { + "type": "string", + "description": "The ports mappings." + }, + "base_directory": { + "type": "string", + "description": "The base directory for all commands." + }, + "health_check_enabled": { + "type": "boolean", + "description": "Health check enabled." + }, + "health_check_path": { + "type": "string", + "description": "Health check path." + }, + "health_check_port": { + "type": "string", + "nullable": true, + "description": "Health check port." + }, + "health_check_host": { + "type": "string", + "nullable": true, + "description": "Health check host." + }, + "health_check_method": { + "type": "string", + "description": "Health check method." + }, + "health_check_return_code": { + "type": "integer", + "description": "Health check return code." + }, + "health_check_scheme": { + "type": "string", + "description": "Health check scheme." + }, + "health_check_response_text": { + "type": "string", + "nullable": true, + "description": "Health check response text." + }, + "health_check_interval": { + "type": "integer", + "description": "Health check interval in seconds." + }, + "health_check_timeout": { + "type": "integer", + "description": "Health check timeout in seconds." + }, + "health_check_retries": { + "type": "integer", + "description": "Health check retries count." + }, + "health_check_start_period": { + "type": "integer", + "description": "Health check start period in seconds." + }, + "limits_memory": { + "type": "string", + "description": "Memory limit." + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit." + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness." + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation." + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit." + }, + "limits_cpuset": { + "type": "string", + "nullable": true, + "description": "CPU set." + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares." + }, + "custom_labels": { + "type": "string", + "description": "Custom labels." + }, + "custom_docker_run_options": { + "type": "string", + "description": "Custom docker run options." + }, + "post_deployment_command": { + "type": "string", + "description": "Post deployment command." + }, + "post_deployment_command_container": { + "type": "string", + "description": "Post deployment command container." + }, + "pre_deployment_command": { + "type": "string", + "description": "Pre deployment command." + }, + "pre_deployment_command_container": { + "type": "string", + "description": "Pre deployment command container." + }, + "manual_webhook_secret_github": { + "type": "string", + "description": "Manual webhook secret for Github." + }, + "manual_webhook_secret_gitlab": { + "type": "string", + "description": "Manual webhook secret for Gitlab." + }, + "manual_webhook_secret_bitbucket": { + "type": "string", + "description": "Manual webhook secret for Bitbucket." + }, + "manual_webhook_secret_gitea": { + "type": "string", + "description": "Manual webhook secret for Gitea." + }, + "redirect": { + "type": "string", + "nullable": true, + "description": "How to set redirect with Traefik \/ Caddy. www<->non-www.", + "enum": [ + "www", + "non-www", + "both" + ] + }, + "instant_deploy": { + "type": "boolean", + "description": "The flag to indicate if the application should be deployed instantly." + }, + "use_build_server": { + "type": "boolean", + "nullable": true, + "description": "Use build server." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Application created successfully." + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/dockerimage": { + "post": { + "tags": [ + "Applications" + ], + "summary": "Create (Docker Image)", + "description": "Create new application based on a prebuilt docker image", + "operationId": "create-dockerimage-application", + "requestBody": { + "description": "Application object that needs to be created.", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "project_uuid", + "server_uuid", + "environment_name", + "docker_registry_image_name", + "ports_exposes" + ], + "properties": { + "project_uuid": { + "type": "string", + "description": "The project UUID." + }, + "server_uuid": { + "type": "string", + "description": "The server UUID." + }, + "environment_name": { + "type": "string", + "description": "The environment name." + }, + "docker_registry_image_name": { + "type": "string", + "description": "The docker registry image name." + }, + "docker_registry_image_tag": { + "type": "string", + "description": "The docker registry image tag." + }, + "ports_exposes": { + "type": "string", + "description": "The ports to expose." + }, + "destination_uuid": { + "type": "string", + "description": "The destination UUID." + }, + "name": { + "type": "string", + "description": "The application name." + }, + "description": { + "type": "string", + "description": "The application description." + }, + "domains": { + "type": "string", + "description": "The application domains." + }, + "ports_mappings": { + "type": "string", + "description": "The ports mappings." + }, + "health_check_enabled": { + "type": "boolean", + "description": "Health check enabled." + }, + "health_check_path": { + "type": "string", + "description": "Health check path." + }, + "health_check_port": { + "type": "string", + "nullable": true, + "description": "Health check port." + }, + "health_check_host": { + "type": "string", + "nullable": true, + "description": "Health check host." + }, + "health_check_method": { + "type": "string", + "description": "Health check method." + }, + "health_check_return_code": { + "type": "integer", + "description": "Health check return code." + }, + "health_check_scheme": { + "type": "string", + "description": "Health check scheme." + }, + "health_check_response_text": { + "type": "string", + "nullable": true, + "description": "Health check response text." + }, + "health_check_interval": { + "type": "integer", + "description": "Health check interval in seconds." + }, + "health_check_timeout": { + "type": "integer", + "description": "Health check timeout in seconds." + }, + "health_check_retries": { + "type": "integer", + "description": "Health check retries count." + }, + "health_check_start_period": { + "type": "integer", + "description": "Health check start period in seconds." + }, + "limits_memory": { + "type": "string", + "description": "Memory limit." + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit." + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness." + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation." + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit." + }, + "limits_cpuset": { + "type": "string", + "nullable": true, + "description": "CPU set." + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares." + }, + "custom_labels": { + "type": "string", + "description": "Custom labels." + }, + "custom_docker_run_options": { + "type": "string", + "description": "Custom docker run options." + }, + "post_deployment_command": { + "type": "string", + "description": "Post deployment command." + }, + "post_deployment_command_container": { + "type": "string", + "description": "Post deployment command container." + }, + "pre_deployment_command": { + "type": "string", + "description": "Pre deployment command." + }, + "pre_deployment_command_container": { + "type": "string", + "description": "Pre deployment command container." + }, + "manual_webhook_secret_github": { + "type": "string", + "description": "Manual webhook secret for Github." + }, + "manual_webhook_secret_gitlab": { + "type": "string", + "description": "Manual webhook secret for Gitlab." + }, + "manual_webhook_secret_bitbucket": { + "type": "string", + "description": "Manual webhook secret for Bitbucket." + }, + "manual_webhook_secret_gitea": { + "type": "string", + "description": "Manual webhook secret for Gitea." + }, + "redirect": { + "type": "string", + "nullable": true, + "description": "How to set redirect with Traefik \/ Caddy. www<->non-www.", + "enum": [ + "www", + "non-www", + "both" + ] + }, + "instant_deploy": { + "type": "boolean", + "description": "The flag to indicate if the application should be deployed instantly." + }, + "use_build_server": { + "type": "boolean", + "nullable": true, + "description": "Use build server." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Application created successfully." + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/dockercompose": { + "post": { + "tags": [ + "Applications" + ], + "summary": "Create (Docker Compose)", + "description": "Create new application based on a docker-compose file.", + "operationId": "create-dockercompose-application", + "requestBody": { + "description": "Application object that needs to be created.", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "project_uuid", + "server_uuid", + "environment_name", + "docker_compose_raw" + ], + "properties": { + "project_uuid": { + "type": "string", + "description": "The project UUID." + }, + "server_uuid": { + "type": "string", + "description": "The server UUID." + }, + "environment_name": { + "type": "string", + "description": "The environment name." + }, + "docker_compose_raw": { + "type": "string", + "description": "The Docker Compose raw content." + }, + "destination_uuid": { + "type": "string", + "description": "The destination UUID if the server has more than one destinations." + }, + "name": { + "type": "string", + "description": "The application name." + }, + "description": { + "type": "string", + "description": "The application description." + }, + "instant_deploy": { + "type": "boolean", + "description": "The flag to indicate if the application should be deployed instantly." + }, + "use_build_server": { + "type": "boolean", + "nullable": true, + "description": "Use build server." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Application created successfully." + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/{uuid}": { + "get": { + "tags": [ + "Applications" + ], + "summary": "Get", + "description": "Get application by UUID.", + "operationId": "get-application-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Get application by UUID.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/Application" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": [ + "Applications" + ], + "summary": "Delete", + "description": "Delete application by UUID.", + "operationId": "delete-application-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "delete_configurations", + "in": "query", + "description": "Delete configurations.", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "delete_volumes", + "in": "query", + "description": "Delete volumes.", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "docker_cleanup", + "in": "query", + "description": "Run docker cleanup.", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "delete_connected_networks", + "in": "query", + "description": "Delete connected networks.", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Application deleted.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Application deleted." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "patch": { + "tags": [ + "Applications" + ], + "summary": "Update", + "description": "Update application by UUID.", + "operationId": "update-application-by-uuid", + "requestBody": { + "description": "Application updated.", + "required": true, + "content": { + "application\/json": { + "schema": { + "properties": { + "project_uuid": { + "type": "string", + "description": "The project UUID." + }, + "server_uuid": { + "type": "string", + "description": "The server UUID." + }, + "environment_name": { + "type": "string", + "description": "The environment name." + }, + "github_app_uuid": { + "type": "string", + "description": "The Github App UUID." + }, + "git_repository": { + "type": "string", + "description": "The git repository URL." + }, + "git_branch": { + "type": "string", + "description": "The git branch." + }, + "ports_exposes": { + "type": "string", + "description": "The ports to expose." + }, + "destination_uuid": { + "type": "string", + "description": "The destination UUID." + }, + "build_pack": { + "type": "string", + "enum": [ + "nixpacks", + "static", + "dockerfile", + "dockercompose" + ], + "description": "The build pack type." + }, + "name": { + "type": "string", + "description": "The application name." + }, + "description": { + "type": "string", + "description": "The application description." + }, + "domains": { + "type": "string", + "description": "The application domains." + }, + "git_commit_sha": { + "type": "string", + "description": "The git commit SHA." + }, + "docker_registry_image_name": { + "type": "string", + "description": "The docker registry image name." + }, + "docker_registry_image_tag": { + "type": "string", + "description": "The docker registry image tag." + }, + "is_static": { + "type": "boolean", + "description": "The flag to indicate if the application is static." + }, + "install_command": { + "type": "string", + "description": "The install command." + }, + "build_command": { + "type": "string", + "description": "The build command." + }, + "start_command": { + "type": "string", + "description": "The start command." + }, + "ports_mappings": { + "type": "string", + "description": "The ports mappings." + }, + "base_directory": { + "type": "string", + "description": "The base directory for all commands." + }, + "publish_directory": { + "type": "string", + "description": "The publish directory." + }, + "health_check_enabled": { + "type": "boolean", + "description": "Health check enabled." + }, + "health_check_path": { + "type": "string", + "description": "Health check path." + }, + "health_check_port": { + "type": "string", + "nullable": true, + "description": "Health check port." + }, + "health_check_host": { + "type": "string", + "nullable": true, + "description": "Health check host." + }, + "health_check_method": { + "type": "string", + "description": "Health check method." + }, + "health_check_return_code": { + "type": "integer", + "description": "Health check return code." + }, + "health_check_scheme": { + "type": "string", + "description": "Health check scheme." + }, + "health_check_response_text": { + "type": "string", + "nullable": true, + "description": "Health check response text." + }, + "health_check_interval": { + "type": "integer", + "description": "Health check interval in seconds." + }, + "health_check_timeout": { + "type": "integer", + "description": "Health check timeout in seconds." + }, + "health_check_retries": { + "type": "integer", + "description": "Health check retries count." + }, + "health_check_start_period": { + "type": "integer", + "description": "Health check start period in seconds." + }, + "limits_memory": { + "type": "string", + "description": "Memory limit." + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit." + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness." + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation." + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit." + }, + "limits_cpuset": { + "type": "string", + "nullable": true, + "description": "CPU set." + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares." + }, + "custom_labels": { + "type": "string", + "description": "Custom labels." + }, + "custom_docker_run_options": { + "type": "string", + "description": "Custom docker run options." + }, + "post_deployment_command": { + "type": "string", + "description": "Post deployment command." + }, + "post_deployment_command_container": { + "type": "string", + "description": "Post deployment command container." + }, + "pre_deployment_command": { + "type": "string", + "description": "Pre deployment command." + }, + "pre_deployment_command_container": { + "type": "string", + "description": "Pre deployment command container." + }, + "manual_webhook_secret_github": { + "type": "string", + "description": "Manual webhook secret for Github." + }, + "manual_webhook_secret_gitlab": { + "type": "string", + "description": "Manual webhook secret for Gitlab." + }, + "manual_webhook_secret_bitbucket": { + "type": "string", + "description": "Manual webhook secret for Bitbucket." + }, + "manual_webhook_secret_gitea": { + "type": "string", + "description": "Manual webhook secret for Gitea." + }, + "redirect": { + "type": "string", + "nullable": true, + "description": "How to set redirect with Traefik \/ Caddy. www<->non-www.", + "enum": [ + "www", + "non-www", + "both" + ] + }, + "instant_deploy": { + "type": "boolean", + "description": "The flag to indicate if the application should be deployed instantly." + }, + "dockerfile": { + "type": "string", + "description": "The Dockerfile content." + }, + "docker_compose_location": { + "type": "string", + "description": "The Docker Compose location." + }, + "docker_compose_raw": { + "type": "string", + "description": "The Docker Compose raw content." + }, + "docker_compose_custom_start_command": { + "type": "string", + "description": "The Docker Compose custom start command." + }, + "docker_compose_custom_build_command": { + "type": "string", + "description": "The Docker Compose custom build command." + }, + "docker_compose_domains": { + "type": "array", + "description": "The Docker Compose domains." + }, + "watch_paths": { + "type": "string", + "description": "The watch paths." + }, + "use_build_server": { + "type": "boolean", + "nullable": true, + "description": "Use build server." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Application updated.", + "content": { + "application\/json": { + "schema": { + "properties": { + "uuid": { + "type": "string" + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/{uuid}\/envs": { + "get": { + "tags": [ + "Applications" + ], + "summary": "List Envs", + "description": "List all envs by application UUID.", + "operationId": "list-envs-by-application-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "All environment variables by application UUID.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/EnvironmentVariable" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": [ + "Applications" + ], + "summary": "Create Env", + "description": "Create env by application UUID.", + "operationId": "create-env-by-application-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "Env created.", + "required": true, + "content": { + "application\/json": { + "schema": { + "properties": { + "key": { + "type": "string", + "description": "The key of the environment variable." + }, + "value": { + "type": "string", + "description": "The value of the environment variable." + }, + "is_preview": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is used in preview deployments." + }, + "is_build_time": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is used in build time." + }, + "is_literal": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is a literal, nothing espaced." + }, + "is_multiline": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is multiline." + }, + "is_shown_once": { + "type": "boolean", + "description": "The flag to indicate if the environment variable's value is shown on the UI." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Environment variable created.", + "content": { + "application\/json": { + "schema": { + "properties": { + "uuid": { + "type": "string", + "example": "nc0k04gk8g0cgsk440g0koko" + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "patch": { + "tags": [ + "Applications" + ], + "summary": "Update Env", + "description": "Update env by application UUID.", + "operationId": "update-env-by-application-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "Env updated.", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string", + "description": "The key of the environment variable." + }, + "value": { + "type": "string", + "description": "The value of the environment variable." + }, + "is_preview": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is used in preview deployments." + }, + "is_build_time": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is used in build time." + }, + "is_literal": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is a literal, nothing espaced." + }, + "is_multiline": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is multiline." + }, + "is_shown_once": { + "type": "boolean", + "description": "The flag to indicate if the environment variable's value is shown on the UI." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Environment variable updated.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Environment variable updated." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/{uuid}\/envs\/bulk": { + "patch": { + "tags": [ + "Applications" + ], + "summary": "Update Envs (Bulk)", + "description": "Update multiple envs by application UUID.", + "operationId": "update-envs-by-application-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "Bulk envs updated.", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "key": { + "type": "string", + "description": "The key of the environment variable." + }, + "value": { + "type": "string", + "description": "The value of the environment variable." + }, + "is_preview": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is used in preview deployments." + }, + "is_build_time": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is used in build time." + }, + "is_literal": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is a literal, nothing espaced." + }, + "is_multiline": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is multiline." + }, + "is_shown_once": { + "type": "boolean", + "description": "The flag to indicate if the environment variable's value is shown on the UI." + } + }, + "type": "object" + } + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Environment variables updated.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Environment variables updated." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/{uuid}\/envs\/{env_uuid}": { + "delete": { + "tags": [ + "Applications" + ], + "summary": "Delete Env", + "description": "Delete env by UUID.", + "operationId": "delete-env-by-application-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "env_uuid", + "in": "path", + "description": "UUID of the environment variable.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Environment variable deleted.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Environment variable deleted." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/{uuid}\/start": { + "get": { + "tags": [ + "Applications" + ], + "summary": "Start", + "description": "Start application. `Post` request is also accepted.", + "operationId": "start-application-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "force", + "in": "query", + "description": "Force rebuild.", + "schema": { + "type": "boolean", + "default": false + } + }, + { + "name": "instant_deploy", + "in": "query", + "description": "Instant deploy (skip queuing).", + "schema": { + "type": "boolean", + "default": false + } + } + ], + "responses": { + "200": { + "description": "Start application.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Deployment request queued.", + "description": "Message." + }, + "deployment_uuid": { + "type": "string", + "example": "doogksw", + "description": "UUID of the deployment." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/{uuid}\/stop": { + "get": { + "tags": [ + "Applications" + ], + "summary": "Stop", + "description": "Stop application. `Post` request is also accepted.", + "operationId": "stop-application-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Stop application.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Application stopping request queued." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/{uuid}\/restart": { + "get": { + "tags": [ + "Applications" + ], + "summary": "Restart", + "description": "Restart application. `Post` request is also accepted.", + "operationId": "restart-application-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Restart application.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Restart request queued." + }, + "deployment_uuid": { + "type": "string", + "example": "doogksw", + "description": "UUID of the deployment." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/applications\/{uuid}\/execute": { + "post": { + "tags": [ + "Applications" + ], + "summary": "Execute Command", + "description": "Execute a command on the application's current container.", + "operationId": "execute-command-application", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "Command to execute.", + "required": true, + "content": { + "application\/json": { + "schema": { + "properties": { + "command": { + "type": "string", + "description": "Command to execute." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Execute a command on the application's current container.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Command executed." + }, + "response": { + "type": "string" + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/databases": { + "get": { + "tags": [ + "Databases" + ], + "summary": "List", + "description": "List all databases.", + "operationId": "list-databases", + "responses": { + "200": { + "description": "Get all databases", + "content": { + "application\/json": { + "schema": { + "type": "string" + }, + "example": "Content is very complex. Will be implemented later." + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/databases\/{uuid}": { + "get": { + "tags": [ + "Databases" + ], + "summary": "Get", + "description": "Get database by UUID.", + "operationId": "get-database-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the database.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Get all databases", + "content": { + "application\/json": { + "schema": { + "type": "string" + }, + "example": "Content is very complex. Will be implemented later." + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": [ + "Databases" + ], + "summary": "Delete", + "description": "Delete database by UUID.", + "operationId": "delete-database-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the database.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "delete_configurations", + "in": "query", + "description": "Delete configurations.", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "delete_volumes", + "in": "query", + "description": "Delete volumes.", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "docker_cleanup", + "in": "query", + "description": "Run docker cleanup.", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "delete_connected_networks", + "in": "query", + "description": "Delete connected networks.", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Database deleted.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Database deleted." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "patch": { + "tags": [ + "Databases" + ], + "summary": "Update", + "description": "Update database by UUID.", + "operationId": "update-database-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the database.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "Database data", + "required": true, + "content": { + "application\/json": { + "schema": { + "properties": { + "name": { + "type": "string", + "description": "Name of the database" + }, + "description": { + "type": "string", + "description": "Description of the database" + }, + "image": { + "type": "string", + "description": "Docker Image of the database" + }, + "is_public": { + "type": "boolean", + "description": "Is the database public?" + }, + "public_port": { + "type": "integer", + "description": "Public port of the database" + }, + "limits_memory": { + "type": "string", + "description": "Memory limit of the database" + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit of the database" + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness of the database" + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation of the database" + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit of the database" + }, + "limits_cpuset": { + "type": "string", + "description": "CPU set of the database" + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares of the database" + }, + "postgres_user": { + "type": "string", + "description": "PostgreSQL user" + }, + "postgres_password": { + "type": "string", + "description": "PostgreSQL password" + }, + "postgres_db": { + "type": "string", + "description": "PostgreSQL database" + }, + "postgres_initdb_args": { + "type": "string", + "description": "PostgreSQL initdb args" + }, + "postgres_host_auth_method": { + "type": "string", + "description": "PostgreSQL host auth method" + }, + "postgres_conf": { + "type": "string", + "description": "PostgreSQL conf" + }, + "clickhouse_admin_user": { + "type": "string", + "description": "Clickhouse admin user" + }, + "clickhouse_admin_password": { + "type": "string", + "description": "Clickhouse admin password" + }, + "dragonfly_password": { + "type": "string", + "description": "DragonFly password" + }, + "redis_password": { + "type": "string", + "description": "Redis password" + }, + "redis_conf": { + "type": "string", + "description": "Redis conf" + }, + "keydb_password": { + "type": "string", + "description": "KeyDB password" + }, + "keydb_conf": { + "type": "string", + "description": "KeyDB conf" + }, + "mariadb_conf": { + "type": "string", + "description": "MariaDB conf" + }, + "mariadb_root_password": { + "type": "string", + "description": "MariaDB root password" + }, + "mariadb_user": { + "type": "string", + "description": "MariaDB user" + }, + "mariadb_password": { + "type": "string", + "description": "MariaDB password" + }, + "mariadb_database": { + "type": "string", + "description": "MariaDB database" + }, + "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" + }, + "mysql_root_password": { + "type": "string", + "description": "MySQL root password" + }, + "mysql_user": { + "type": "string", + "description": "MySQL user" + }, + "mysql_database": { + "type": "string", + "description": "MySQL database" + }, + "mysql_conf": { + "type": "string", + "description": "MySQL conf" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Database updated" + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/databases\/postgresql": { + "post": { + "tags": [ + "Databases" + ], + "summary": "Create (PostgreSQL)", + "description": "Create a new PostgreSQL database.", + "operationId": "create-database-postgresql", + "requestBody": { + "description": "Database data", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "server_uuid", + "project_uuid", + "environment_name" + ], + "properties": { + "server_uuid": { + "type": "string", + "description": "UUID of the server" + }, + "project_uuid": { + "type": "string", + "description": "UUID of the project" + }, + "environment_name": { + "type": "string", + "description": "Name of the environment" + }, + "postgres_user": { + "type": "string", + "description": "PostgreSQL user" + }, + "postgres_password": { + "type": "string", + "description": "PostgreSQL password" + }, + "postgres_db": { + "type": "string", + "description": "PostgreSQL database" + }, + "postgres_initdb_args": { + "type": "string", + "description": "PostgreSQL initdb args" + }, + "postgres_host_auth_method": { + "type": "string", + "description": "PostgreSQL host auth method" + }, + "postgres_conf": { + "type": "string", + "description": "PostgreSQL conf" + }, + "destination_uuid": { + "type": "string", + "description": "UUID of the destination if the server has multiple destinations" + }, + "name": { + "type": "string", + "description": "Name of the database" + }, + "description": { + "type": "string", + "description": "Description of the database" + }, + "image": { + "type": "string", + "description": "Docker Image of the database" + }, + "is_public": { + "type": "boolean", + "description": "Is the database public?" + }, + "public_port": { + "type": "integer", + "description": "Public port of the database" + }, + "limits_memory": { + "type": "string", + "description": "Memory limit of the database" + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit of the database" + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness of the database" + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation of the database" + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit of the database" + }, + "limits_cpuset": { + "type": "string", + "description": "CPU set of the database" + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares of the database" + }, + "instant_deploy": { + "type": "boolean", + "description": "Instant deploy the database" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Database updated" + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/databases\/clickhouse": { + "post": { + "tags": [ + "Databases" + ], + "summary": "Create (Clickhouse)", + "description": "Create a new Clickhouse database.", + "operationId": "create-database-clickhouse", + "requestBody": { + "description": "Database data", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "server_uuid", + "project_uuid", + "environment_name" + ], + "properties": { + "server_uuid": { + "type": "string", + "description": "UUID of the server" + }, + "project_uuid": { + "type": "string", + "description": "UUID of the project" + }, + "environment_name": { + "type": "string", + "description": "Name of the environment" + }, + "destination_uuid": { + "type": "string", + "description": "UUID of the destination if the server has multiple destinations" + }, + "clickhouse_admin_user": { + "type": "string", + "description": "Clickhouse admin user" + }, + "clickhouse_admin_password": { + "type": "string", + "description": "Clickhouse admin password" + }, + "name": { + "type": "string", + "description": "Name of the database" + }, + "description": { + "type": "string", + "description": "Description of the database" + }, + "image": { + "type": "string", + "description": "Docker Image of the database" + }, + "is_public": { + "type": "boolean", + "description": "Is the database public?" + }, + "public_port": { + "type": "integer", + "description": "Public port of the database" + }, + "limits_memory": { + "type": "string", + "description": "Memory limit of the database" + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit of the database" + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness of the database" + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation of the database" + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit of the database" + }, + "limits_cpuset": { + "type": "string", + "description": "CPU set of the database" + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares of the database" + }, + "instant_deploy": { + "type": "boolean", + "description": "Instant deploy the database" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Database updated" + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/databases\/dragonfly": { + "post": { + "tags": [ + "Databases" + ], + "summary": "Create (DragonFly)", + "description": "Create a new DragonFly database.", + "operationId": "create-database-dragonfly", + "requestBody": { + "description": "Database data", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "server_uuid", + "project_uuid", + "environment_name" + ], + "properties": { + "server_uuid": { + "type": "string", + "description": "UUID of the server" + }, + "project_uuid": { + "type": "string", + "description": "UUID of the project" + }, + "environment_name": { + "type": "string", + "description": "Name of the environment" + }, + "destination_uuid": { + "type": "string", + "description": "UUID of the destination if the server has multiple destinations" + }, + "dragonfly_password": { + "type": "string", + "description": "DragonFly password" + }, + "name": { + "type": "string", + "description": "Name of the database" + }, + "description": { + "type": "string", + "description": "Description of the database" + }, + "image": { + "type": "string", + "description": "Docker Image of the database" + }, + "is_public": { + "type": "boolean", + "description": "Is the database public?" + }, + "public_port": { + "type": "integer", + "description": "Public port of the database" + }, + "limits_memory": { + "type": "string", + "description": "Memory limit of the database" + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit of the database" + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness of the database" + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation of the database" + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit of the database" + }, + "limits_cpuset": { + "type": "string", + "description": "CPU set of the database" + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares of the database" + }, + "instant_deploy": { + "type": "boolean", + "description": "Instant deploy the database" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Database updated" + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/databases\/redis": { + "post": { + "tags": [ + "Databases" + ], + "summary": "Create (Redis)", + "description": "Create a new Redis database.", + "operationId": "create-database-redis", + "requestBody": { + "description": "Database data", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "server_uuid", + "project_uuid", + "environment_name" + ], + "properties": { + "server_uuid": { + "type": "string", + "description": "UUID of the server" + }, + "project_uuid": { + "type": "string", + "description": "UUID of the project" + }, + "environment_name": { + "type": "string", + "description": "Name of the environment" + }, + "destination_uuid": { + "type": "string", + "description": "UUID of the destination if the server has multiple destinations" + }, + "redis_password": { + "type": "string", + "description": "Redis password" + }, + "redis_conf": { + "type": "string", + "description": "Redis conf" + }, + "name": { + "type": "string", + "description": "Name of the database" + }, + "description": { + "type": "string", + "description": "Description of the database" + }, + "image": { + "type": "string", + "description": "Docker Image of the database" + }, + "is_public": { + "type": "boolean", + "description": "Is the database public?" + }, + "public_port": { + "type": "integer", + "description": "Public port of the database" + }, + "limits_memory": { + "type": "string", + "description": "Memory limit of the database" + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit of the database" + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness of the database" + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation of the database" + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit of the database" + }, + "limits_cpuset": { + "type": "string", + "description": "CPU set of the database" + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares of the database" + }, + "instant_deploy": { + "type": "boolean", + "description": "Instant deploy the database" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Database updated" + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/databases\/keydb": { + "post": { + "tags": [ + "Databases" + ], + "summary": "Create (KeyDB)", + "description": "Create a new KeyDB database.", + "operationId": "create-database-keydb", + "requestBody": { + "description": "Database data", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "server_uuid", + "project_uuid", + "environment_name" + ], + "properties": { + "server_uuid": { + "type": "string", + "description": "UUID of the server" + }, + "project_uuid": { + "type": "string", + "description": "UUID of the project" + }, + "environment_name": { + "type": "string", + "description": "Name of the environment" + }, + "destination_uuid": { + "type": "string", + "description": "UUID of the destination if the server has multiple destinations" + }, + "keydb_password": { + "type": "string", + "description": "KeyDB password" + }, + "keydb_conf": { + "type": "string", + "description": "KeyDB conf" + }, + "name": { + "type": "string", + "description": "Name of the database" + }, + "description": { + "type": "string", + "description": "Description of the database" + }, + "image": { + "type": "string", + "description": "Docker Image of the database" + }, + "is_public": { + "type": "boolean", + "description": "Is the database public?" + }, + "public_port": { + "type": "integer", + "description": "Public port of the database" + }, + "limits_memory": { + "type": "string", + "description": "Memory limit of the database" + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit of the database" + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness of the database" + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation of the database" + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit of the database" + }, + "limits_cpuset": { + "type": "string", + "description": "CPU set of the database" + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares of the database" + }, + "instant_deploy": { + "type": "boolean", + "description": "Instant deploy the database" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Database updated" + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/databases\/mariadb": { + "post": { + "tags": [ + "Databases" + ], + "summary": "Create (MariaDB)", + "description": "Create a new MariaDB database.", + "operationId": "create-database-mariadb", + "requestBody": { + "description": "Database data", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "server_uuid", + "project_uuid", + "environment_name" + ], + "properties": { + "server_uuid": { + "type": "string", + "description": "UUID of the server" + }, + "project_uuid": { + "type": "string", + "description": "UUID of the project" + }, + "environment_name": { + "type": "string", + "description": "Name of the environment" + }, + "destination_uuid": { + "type": "string", + "description": "UUID of the destination if the server has multiple destinations" + }, + "mariadb_conf": { + "type": "string", + "description": "MariaDB conf" + }, + "mariadb_root_password": { + "type": "string", + "description": "MariaDB root password" + }, + "mariadb_user": { + "type": "string", + "description": "MariaDB user" + }, + "mariadb_password": { + "type": "string", + "description": "MariaDB password" + }, + "mariadb_database": { + "type": "string", + "description": "MariaDB database" + }, + "name": { + "type": "string", + "description": "Name of the database" + }, + "description": { + "type": "string", + "description": "Description of the database" + }, + "image": { + "type": "string", + "description": "Docker Image of the database" + }, + "is_public": { + "type": "boolean", + "description": "Is the database public?" + }, + "public_port": { + "type": "integer", + "description": "Public port of the database" + }, + "limits_memory": { + "type": "string", + "description": "Memory limit of the database" + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit of the database" + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness of the database" + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation of the database" + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit of the database" + }, + "limits_cpuset": { + "type": "string", + "description": "CPU set of the database" + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares of the database" + }, + "instant_deploy": { + "type": "boolean", + "description": "Instant deploy the database" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Database updated" + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/databases\/mysql": { + "post": { + "tags": [ + "Databases" + ], + "summary": "Create (MySQL)", + "description": "Create a new MySQL database.", + "operationId": "create-database-mysql", + "requestBody": { + "description": "Database data", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "server_uuid", + "project_uuid", + "environment_name" + ], + "properties": { + "server_uuid": { + "type": "string", + "description": "UUID of the server" + }, + "project_uuid": { + "type": "string", + "description": "UUID of the project" + }, + "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_user": { + "type": "string", + "description": "MySQL user" + }, + "mysql_database": { + "type": "string", + "description": "MySQL database" + }, + "mysql_conf": { + "type": "string", + "description": "MySQL conf" + }, + "name": { + "type": "string", + "description": "Name of the database" + }, + "description": { + "type": "string", + "description": "Description of the database" + }, + "image": { + "type": "string", + "description": "Docker Image of the database" + }, + "is_public": { + "type": "boolean", + "description": "Is the database public?" + }, + "public_port": { + "type": "integer", + "description": "Public port of the database" + }, + "limits_memory": { + "type": "string", + "description": "Memory limit of the database" + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit of the database" + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness of the database" + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation of the database" + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit of the database" + }, + "limits_cpuset": { + "type": "string", + "description": "CPU set of the database" + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares of the database" + }, + "instant_deploy": { + "type": "boolean", + "description": "Instant deploy the database" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Database updated" + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/databases\/mongodb": { + "post": { + "tags": [ + "Databases" + ], + "summary": "Create (MongoDB)", + "description": "Create a new MongoDB database.", + "operationId": "create-database-mongodb", + "requestBody": { + "description": "Database data", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "server_uuid", + "project_uuid", + "environment_name" + ], + "properties": { + "server_uuid": { + "type": "string", + "description": "UUID of the server" + }, + "project_uuid": { + "type": "string", + "description": "UUID of the project" + }, + "environment_name": { + "type": "string", + "description": "Name of the environment" + }, + "destination_uuid": { + "type": "string", + "description": "UUID of the destination if the server has multiple destinations" + }, + "mongo_conf": { + "type": "string", + "description": "MongoDB conf" + }, + "mongo_initdb_root_username": { + "type": "string", + "description": "MongoDB initdb root username" + }, + "name": { + "type": "string", + "description": "Name of the database" + }, + "description": { + "type": "string", + "description": "Description of the database" + }, + "image": { + "type": "string", + "description": "Docker Image of the database" + }, + "is_public": { + "type": "boolean", + "description": "Is the database public?" + }, + "public_port": { + "type": "integer", + "description": "Public port of the database" + }, + "limits_memory": { + "type": "string", + "description": "Memory limit of the database" + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit of the database" + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness of the database" + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation of the database" + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit of the database" + }, + "limits_cpuset": { + "type": "string", + "description": "CPU set of the database" + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares of the database" + }, + "instant_deploy": { + "type": "boolean", + "description": "Instant deploy the database" + } + }, + "type": "object" + } + } + } + }, + "responses": { + "200": { + "description": "Database updated" + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/databases\/{uuid}\/start": { + "get": { + "tags": [ + "Databases" + ], + "summary": "Start", + "description": "Start database. `Post` request is also accepted.", + "operationId": "start-database-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the database.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Start database.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Database starting request queued." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/databases\/{uuid}\/stop": { + "get": { + "tags": [ + "Databases" + ], + "summary": "Stop", + "description": "Stop database. `Post` request is also accepted.", + "operationId": "stop-database-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the database.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Stop database.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Database stopping request queued." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/databases\/{uuid}\/restart": { + "get": { + "tags": [ + "Databases" + ], + "summary": "Restart", + "description": "Restart database. `Post` request is also accepted.", + "operationId": "restart-database-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the database.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Restart database.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Database restaring request queued." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/deployments": { + "get": { + "tags": [ + "Deployments" + ], + "summary": "List", + "description": "List currently running deployments", + "operationId": "list-deployments", + "responses": { + "200": { + "description": "Get all currently running deployments.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/ApplicationDeploymentQueue" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/deployments\/{uuid}": { + "get": { + "tags": [ + "Deployments" + ], + "summary": "Get", + "description": "Get deployment by UUID.", + "operationId": "get-deployment-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Deployment UUID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Get deployment by UUID.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/ApplicationDeploymentQueue" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/deploy": { + "get": { + "tags": [ + "Deployments" + ], + "summary": "Deploy", + "description": "Deploy by tag or uuid. `Post` request also accepted.", + "operationId": "deploy-by-tag-or-uuid", + "parameters": [ + { + "name": "tag", + "in": "query", + "description": "Tag name(s). Comma separated list is also accepted.", + "schema": { + "type": "string" + } + }, + { + "name": "uuid", + "in": "query", + "description": "Resource UUID(s). Comma separated list is also accepted.", + "schema": { + "type": "string" + } + }, + { + "name": "force", + "in": "query", + "description": "Force rebuild (without cache)", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "description": "Get deployment(s) UUID's", + "content": { + "application\/json": { + "schema": { + "properties": { + "deployments": { + "type": "array", + "items": { + "properties": { + "message": { + "type": "string" + }, + "resource_uuid": { + "type": "string" + }, + "deployment_uuid": { + "type": "string" + } + }, + "type": "object" + } + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/version": { + "get": { + "summary": "Version", + "description": "Get Coolify version.", + "operationId": "version", + "responses": { + "200": { + "description": "Returns the version of the application", + "content": { + "application\/json": { + "schema": { + "type": "string" + }, + "example": "v4.0.0" + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/enable": { + "get": { + "summary": "Enable API", + "description": "Enable API (only with root permissions).", + "operationId": "enable-api", + "responses": { + "200": { + "description": "Enable API.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "API enabled." + } + }, + "type": "object" + } + } + } + }, + "403": { + "description": "You are not allowed to enable the API.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "You are not allowed to enable the API." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/disable": { + "get": { + "summary": "Disable API", + "description": "Disable API (only with root permissions).", + "operationId": "disable-api", + "responses": { + "200": { + "description": "Disable API.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "API disabled." + } + }, + "type": "object" + } + } + } + }, + "403": { + "description": "You are not allowed to disable the API.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "You are not allowed to disable the API." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/health": { + "get": { + "summary": "Healthcheck", + "description": "Healthcheck endpoint.", + "operationId": "healthcheck", + "responses": { + "200": { + "description": "Healthcheck endpoint.", + "content": { + "application\/json": { + "schema": { + "type": "string" + }, + "example": "OK" + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + } + } + }, + "\/projects": { + "get": { + "tags": [ + "Projects" + ], + "summary": "List", + "description": "List projects.", + "operationId": "list-projects", + "responses": { + "200": { + "description": "Get all projects.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/Project" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": [ + "Projects" + ], + "summary": "Create", + "description": "Create Project.", + "operationId": "create-project", + "requestBody": { + "description": "Project created.", + "required": true, + "content": { + "application\/json": { + "schema": { + "properties": { + "name": { + "type": "string", + "description": "The name of the project." + }, + "description": { + "type": "string", + "description": "The description of the project." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Project created.", + "content": { + "application\/json": { + "schema": { + "properties": { + "uuid": { + "type": "string", + "example": "og888os", + "description": "The UUID of the project." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/projects\/{uuid}": { + "get": { + "tags": [ + "Projects" + ], + "summary": "Get", + "description": "Get project by UUID.", + "operationId": "get-project-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Project UUID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Project details", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/Project" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "description": "Project not found." + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": [ + "Projects" + ], + "summary": "Delete", + "description": "Delete project by UUID.", + "operationId": "delete-project-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the application.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Project deleted.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Project deleted." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "patch": { + "tags": [ + "Projects" + ], + "summary": "Update", + "description": "Update Project.", + "operationId": "update-project-by-uuid", + "requestBody": { + "description": "Project updated.", + "required": true, + "content": { + "application\/json": { + "schema": { + "properties": { + "name": { + "type": "string", + "description": "The name of the project." + }, + "description": { + "type": "string", + "description": "The description of the project." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Project updated.", + "content": { + "application\/json": { + "schema": { + "properties": { + "uuid": { + "type": "string", + "example": "og888os" + }, + "name": { + "type": "string", + "example": "Project Name" + }, + "description": { + "type": "string", + "example": "Project Description" + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/projects\/{uuid}\/{environment_name}": { + "get": { + "tags": [ + "Projects" + ], + "summary": "Environment", + "description": "Get environment by name.", + "operationId": "get-environment-by-name", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Project UUID", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "environment_name", + "in": "path", + "description": "Environment name", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Environment details", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/Environment" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/resources": { + "get": { + "tags": [ + "Resources" + ], + "summary": "List", + "description": "Get all resources.", + "operationId": "list-resources", + "responses": { + "200": { + "description": "Get all resources", + "content": { + "application\/json": { + "schema": { + "type": "string" + }, + "example": "Content is very complex. Will be implemented later." + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/security\/keys": { + "get": { + "tags": [ + "Private Keys" + ], + "summary": "List", + "description": "List all private keys.", + "operationId": "list-private-keys", + "responses": { + "200": { + "description": "Get all private keys.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/PrivateKey" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": [ + "Private Keys" + ], + "summary": "Create", + "description": "Create a new private key.", + "operationId": "create-private-key", + "requestBody": { + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "private_key" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "private_key": { + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + } + } + }, + "responses": { + "201": { + "description": "The created private key's UUID.", + "content": { + "application\/json": { + "schema": { + "properties": { + "uuid": { + "type": "string" + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "patch": { + "tags": [ + "Private Keys" + ], + "summary": "Update", + "description": "Update a private key.", + "operationId": "update-private-key", + "requestBody": { + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "private_key" + ], + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "private_key": { + "type": "string" + } + }, + "type": "object", + "additionalProperties": false + } + } + } + }, + "responses": { + "201": { + "description": "The updated private key's UUID.", + "content": { + "application\/json": { + "schema": { + "properties": { + "uuid": { + "type": "string" + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/security\/keys\/{uuid}": { + "get": { + "tags": [ + "Private Keys" + ], + "summary": "Get", + "description": "Get key by UUID.", + "operationId": "get-private-key-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Private Key UUID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Get all private keys.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/PrivateKey" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "description": "Private Key not found." + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": [ + "Private Keys" + ], + "summary": "Delete", + "description": "Delete a private key.", + "operationId": "delete-private-key-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Private Key UUID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Private Key deleted.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Private Key deleted." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "description": "Private Key not found." + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/servers": { + "get": { + "tags": [ + "Servers" + ], + "summary": "List", + "description": "List all servers.", + "operationId": "list-servers", + "responses": { + "200": { + "description": "Get all servers.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/Server" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": [ + "Servers" + ], + "summary": "Create", + "description": "Create Server.", + "operationId": "create-server", + "requestBody": { + "description": "Server created.", + "required": true, + "content": { + "application\/json": { + "schema": { + "properties": { + "name": { + "type": "string", + "example": "My Server", + "description": "The name of the server." + }, + "description": { + "type": "string", + "example": "My Server Description", + "description": "The description of the server." + }, + "ip": { + "type": "string", + "example": "127.0.0.1", + "description": "The IP of the server." + }, + "port": { + "type": "integer", + "example": 22, + "description": "The port of the server." + }, + "user": { + "type": "string", + "example": "root", + "description": "The user of the server." + }, + "private_key_uuid": { + "type": "string", + "example": "og888os", + "description": "The UUID of the private key." + }, + "is_build_server": { + "type": "boolean", + "example": false, + "description": "Is build server." + }, + "instant_validate": { + "type": "boolean", + "example": false, + "description": "Instant validate." + }, + "proxy_type": { + "type": "string", + "enum": [ + "traefik", + "caddy", + "none" + ], + "example": "traefik", + "description": "The proxy type." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Server created.", + "content": { + "application\/json": { + "schema": { + "properties": { + "uuid": { + "type": "string", + "example": "og888os", + "description": "The UUID of the server." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/servers\/{uuid}": { + "get": { + "tags": [ + "Servers" + ], + "summary": "Get", + "description": "Get server by UUID.", + "operationId": "get-server-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Server's UUID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Get server by UUID", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/Server" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": [ + "Servers" + ], + "summary": "Delete", + "description": "Delete server by UUID.", + "operationId": "delete-server-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the server.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Server deleted.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Server deleted." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "patch": { + "tags": [ + "Servers" + ], + "summary": "Update", + "description": "Update Server.", + "operationId": "update-server-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Server UUID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Server updated.", + "required": true, + "content": { + "application\/json": { + "schema": { + "properties": { + "name": { + "type": "string", + "description": "The name of the server." + }, + "description": { + "type": "string", + "description": "The description of the server." + }, + "ip": { + "type": "string", + "description": "The IP of the server." + }, + "port": { + "type": "integer", + "description": "The port of the server." + }, + "user": { + "type": "string", + "description": "The user of the server." + }, + "private_key_uuid": { + "type": "string", + "description": "The UUID of the private key." + }, + "is_build_server": { + "type": "boolean", + "description": "Is build server." + }, + "instant_validate": { + "type": "boolean", + "description": "Instant validate." + }, + "proxy_type": { + "type": "string", + "enum": [ + "traefik", + "caddy", + "none" + ], + "description": "The proxy type." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Server updated.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/Server" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/servers\/{uuid}\/resources": { + "get": { + "tags": [ + "Servers" + ], + "summary": "Resources", + "description": "Get resources by server.", + "operationId": "get-resources-by-server-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Server's UUID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Get resources by server", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "properties": { + "id": { + "type": "integer" + }, + "uuid": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "type": "object" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/servers\/{uuid}\/domains": { + "get": { + "tags": [ + "Servers" + ], + "summary": "Domains", + "description": "Get domains by server.", + "operationId": "get-domains-by-server-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Server's UUID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Get domains by server", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "properties": { + "ip": { + "type": "string" + }, + "domains": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "type": "object" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/servers\/{uuid}\/validate": { + "get": { + "tags": [ + "Servers" + ], + "summary": "Validate", + "description": "Validate server by UUID.", + "operationId": "validate-server-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Server UUID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "201": { + "description": "Server validation started.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Validation started." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/services": { + "get": { + "tags": [ + "Services" + ], + "summary": "List", + "description": "List all services.", + "operationId": "list-services", + "responses": { + "200": { + "description": "Get all services", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/Service" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": [ + "Services" + ], + "summary": "Create", + "description": "Create a one-click service", + "operationId": "create-service", + "requestBody": { + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "server_uuid", + "project_uuid", + "environment_name", + "type" + ], + "properties": { + "type": { + "description": "The one-click service type", + "type": "string", + "enum": [ + "activepieces", + "appsmith", + "appwrite", + "authentik", + "babybuddy", + "budge", + "changedetection", + "chatwoot", + "classicpress-with-mariadb", + "classicpress-with-mysql", + "classicpress-without-database", + "cloudflared", + "code-server", + "dashboard", + "directus", + "directus-with-postgresql", + "docker-registry", + "docuseal", + "docuseal-with-postgres", + "dokuwiki", + "duplicati", + "emby", + "embystat", + "fider", + "filebrowser", + "firefly", + "formbricks", + "ghost", + "gitea", + "gitea-with-mariadb", + "gitea-with-mysql", + "gitea-with-postgresql", + "glance", + "glances", + "glitchtip", + "grafana", + "grafana-with-postgresql", + "grocy", + "heimdall", + "homepage", + "jellyfin", + "kuzzle", + "listmonk", + "logto", + "mediawiki", + "meilisearch", + "metabase", + "metube", + "minio", + "moodle", + "n8n", + "n8n-with-postgresql", + "next-image-transformation", + "nextcloud", + "nocodb", + "odoo", + "openblocks", + "pairdrop", + "penpot", + "phpmyadmin", + "pocketbase", + "posthog", + "reactive-resume", + "rocketchat", + "shlink", + "slash", + "snapdrop", + "statusnook", + "stirling-pdf", + "supabase", + "syncthing", + "tolgee", + "trigger", + "trigger-with-external-database", + "twenty", + "umami", + "unleash-with-postgresql", + "unleash-without-database", + "uptime-kuma", + "vaultwarden", + "vikunja", + "weblate", + "whoogle", + "wordpress-with-mariadb", + "wordpress-with-mysql", + "wordpress-without-database" + ] + }, + "name": { + "type": "string", + "maxLength": 255, + "description": "Name of the service." + }, + "description": { + "type": "string", + "nullable": true, + "description": "Description of the service." + }, + "project_uuid": { + "type": "string", + "description": "Project UUID." + }, + "environment_name": { + "type": "string", + "description": "Environment name." + }, + "server_uuid": { + "type": "string", + "description": "Server UUID." + }, + "destination_uuid": { + "type": "string", + "description": "Destination UUID. Required if server has multiple destinations." + }, + "instant_deploy": { + "type": "boolean", + "default": false, + "description": "Start the service immediately after creation." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Create a service.", + "content": { + "application\/json": { + "schema": { + "properties": { + "uuid": { + "type": "string", + "description": "Service UUID." + }, + "domains": { + "type": "array", + "items": { + "type": "string" + }, + "description": "Service domains." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/services\/{uuid}": { + "get": { + "tags": [ + "Services" + ], + "summary": "Get", + "description": "Get service by UUID.", + "operationId": "get-service-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Service UUID", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Get a service by UUID.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/Service" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "delete": { + "tags": [ + "Services" + ], + "summary": "Delete", + "description": "Delete service by UUID.", + "operationId": "delete-service-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "Service UUID", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "delete_configurations", + "in": "query", + "description": "Delete configurations.", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "delete_volumes", + "in": "query", + "description": "Delete volumes.", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "docker_cleanup", + "in": "query", + "description": "Run docker cleanup.", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + }, + { + "name": "delete_connected_networks", + "in": "query", + "description": "Delete connected networks.", + "required": false, + "schema": { + "type": "boolean", + "default": true + } + } + ], + "responses": { + "200": { + "description": "Delete a service by UUID", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Service deletion request queued." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/services\/{uuid}\/envs": { + "get": { + "tags": [ + "Services" + ], + "summary": "List Envs", + "description": "List all envs by service UUID.", + "operationId": "list-envs-by-service-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "All environment variables by service UUID.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/EnvironmentVariable" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "post": { + "tags": [ + "Services" + ], + "summary": "Create Env", + "description": "Create env by service UUID.", + "operationId": "create-env-by-service-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "Env created.", + "required": true, + "content": { + "application\/json": { + "schema": { + "properties": { + "key": { + "type": "string", + "description": "The key of the environment variable." + }, + "value": { + "type": "string", + "description": "The value of the environment variable." + }, + "is_preview": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is used in preview deployments." + }, + "is_build_time": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is used in build time." + }, + "is_literal": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is a literal, nothing espaced." + }, + "is_multiline": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is multiline." + }, + "is_shown_once": { + "type": "boolean", + "description": "The flag to indicate if the environment variable's value is shown on the UI." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Environment variable created.", + "content": { + "application\/json": { + "schema": { + "properties": { + "uuid": { + "type": "string", + "example": "nc0k04gk8g0cgsk440g0koko" + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + }, + "patch": { + "tags": [ + "Services" + ], + "summary": "Update Env", + "description": "Update env by service UUID.", + "operationId": "update-env-by-service-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "Env updated.", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "key", + "value" + ], + "properties": { + "key": { + "type": "string", + "description": "The key of the environment variable." + }, + "value": { + "type": "string", + "description": "The value of the environment variable." + }, + "is_preview": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is used in preview deployments." + }, + "is_build_time": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is used in build time." + }, + "is_literal": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is a literal, nothing espaced." + }, + "is_multiline": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is multiline." + }, + "is_shown_once": { + "type": "boolean", + "description": "The flag to indicate if the environment variable's value is shown on the UI." + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Environment variable updated.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Environment variable updated." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/services\/{uuid}\/envs\/bulk": { + "patch": { + "tags": [ + "Services" + ], + "summary": "Update Envs (Bulk)", + "description": "Update multiple envs by service UUID.", + "operationId": "update-envs-by-service-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "description": "Bulk envs updated.", + "required": true, + "content": { + "application\/json": { + "schema": { + "required": [ + "data" + ], + "properties": { + "data": { + "type": "array", + "items": { + "properties": { + "key": { + "type": "string", + "description": "The key of the environment variable." + }, + "value": { + "type": "string", + "description": "The value of the environment variable." + }, + "is_preview": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is used in preview deployments." + }, + "is_build_time": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is used in build time." + }, + "is_literal": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is a literal, nothing espaced." + }, + "is_multiline": { + "type": "boolean", + "description": "The flag to indicate if the environment variable is multiline." + }, + "is_shown_once": { + "type": "boolean", + "description": "The flag to indicate if the environment variable's value is shown on the UI." + } + }, + "type": "object" + } + } + }, + "type": "object" + } + } + } + }, + "responses": { + "201": { + "description": "Environment variables updated.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Environment variables updated." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/services\/{uuid}\/envs\/{env_uuid}": { + "delete": { + "tags": [ + "Services" + ], + "summary": "Delete Env", + "description": "Delete env by UUID.", + "operationId": "delete-env-by-service-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + }, + { + "name": "env_uuid", + "in": "path", + "description": "UUID of the environment variable.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Environment variable deleted.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Environment variable deleted." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/services\/{uuid}\/start": { + "get": { + "tags": [ + "Services" + ], + "summary": "Start", + "description": "Start service. `Post` request is also accepted.", + "operationId": "start-service-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Start service.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Service starting request queued." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/services\/{uuid}\/stop": { + "get": { + "tags": [ + "Services" + ], + "summary": "Stop", + "description": "Stop service. `Post` request is also accepted.", + "operationId": "stop-service-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Stop service.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Service stopping request queued." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/services\/{uuid}\/restart": { + "get": { + "tags": [ + "Services" + ], + "summary": "Restart", + "description": "Restart service. `Post` request is also accepted.", + "operationId": "restart-service-by-uuid", + "parameters": [ + { + "name": "uuid", + "in": "path", + "description": "UUID of the service.", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Restart service.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Service restaring request queued." + } + }, + "type": "object" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/teams": { + "get": { + "tags": [ + "Teams" + ], + "summary": "List", + "description": "Get all teams.", + "operationId": "list-teams", + "responses": { + "200": { + "description": "List of teams.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/Team" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/teams\/{id}": { + "get": { + "tags": [ + "Teams" + ], + "summary": "Get", + "description": "Get team by TeamId.", + "operationId": "get-team-by-id", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Team ID", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "List of teams.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/Team" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/teams\/{id}\/members": { + "get": { + "tags": [ + "Teams" + ], + "summary": "Members", + "description": "Get members by TeamId.", + "operationId": "get-members-by-team-id", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Team ID", + "required": true, + "schema": { + "type": "integer" + } + } + ], + "responses": { + "200": { + "description": "List of members.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/User" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + }, + "404": { + "$ref": "#\/components\/responses\/404" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/teams\/current": { + "get": { + "tags": [ + "Teams" + ], + "summary": "Authenticated Team", + "description": "Get currently authenticated team.", + "operationId": "get-current-team", + "responses": { + "200": { + "description": "Current Team.", + "content": { + "application\/json": { + "schema": { + "$ref": "#\/components\/schemas\/Team" + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + }, + "\/teams\/current\/members": { + "get": { + "tags": [ + "Teams" + ], + "summary": "Authenticated Team Members", + "description": "Get currently authenticated team members.", + "operationId": "get-current-team-members", + "responses": { + "200": { + "description": "Currently authenticated team members.", + "content": { + "application\/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/User" + } + } + } + } + }, + "401": { + "$ref": "#\/components\/responses\/401" + }, + "400": { + "$ref": "#\/components\/responses\/400" + } + }, + "security": [ + { + "bearerAuth": [] + } + ] + } + } + }, + "components": { + "schemas": { + "Application": { + "description": "Application model", + "properties": { + "id": { + "type": "integer", + "description": "The application identifier in the database." + }, + "description": { + "type": "string", + "nullable": true, + "description": "The application description." + }, + "repository_project_id": { + "type": "integer", + "nullable": true, + "description": "The repository project identifier." + }, + "uuid": { + "type": "string", + "description": "The application UUID." + }, + "name": { + "type": "string", + "description": "The application name." + }, + "fqdn": { + "type": "string", + "nullable": true, + "description": "The application domains." + }, + "config_hash": { + "type": "string", + "description": "Configuration hash." + }, + "git_repository": { + "type": "string", + "description": "Git repository URL." + }, + "git_branch": { + "type": "string", + "description": "Git branch." + }, + "git_commit_sha": { + "type": "string", + "description": "Git commit SHA." + }, + "git_full_url": { + "type": "string", + "nullable": true, + "description": "Git full URL." + }, + "docker_registry_image_name": { + "type": "string", + "nullable": true, + "description": "Docker registry image name." + }, + "docker_registry_image_tag": { + "type": "string", + "nullable": true, + "description": "Docker registry image tag." + }, + "build_pack": { + "type": "string", + "description": "Build pack.", + "enum": [ + "nixpacks", + "static", + "dockerfile", + "dockercompose" + ] + }, + "static_image": { + "type": "string", + "description": "Static image used when static site is deployed." + }, + "install_command": { + "type": "string", + "description": "Install command." + }, + "build_command": { + "type": "string", + "description": "Build command." + }, + "start_command": { + "type": "string", + "description": "Start command." + }, + "ports_exposes": { + "type": "string", + "description": "Ports exposes." + }, + "ports_mappings": { + "type": "string", + "nullable": true, + "description": "Ports mappings." + }, + "base_directory": { + "type": "string", + "description": "Base directory for all commands." + }, + "publish_directory": { + "type": "string", + "description": "Publish directory." + }, + "health_check_enabled": { + "type": "boolean", + "description": "Health check enabled." + }, + "health_check_path": { + "type": "string", + "description": "Health check path." + }, + "health_check_port": { + "type": "string", + "nullable": true, + "description": "Health check port." + }, + "health_check_host": { + "type": "string", + "nullable": true, + "description": "Health check host." + }, + "health_check_method": { + "type": "string", + "description": "Health check method." + }, + "health_check_return_code": { + "type": "integer", + "description": "Health check return code." + }, + "health_check_scheme": { + "type": "string", + "description": "Health check scheme." + }, + "health_check_response_text": { + "type": "string", + "nullable": true, + "description": "Health check response text." + }, + "health_check_interval": { + "type": "integer", + "description": "Health check interval in seconds." + }, + "health_check_timeout": { + "type": "integer", + "description": "Health check timeout in seconds." + }, + "health_check_retries": { + "type": "integer", + "description": "Health check retries count." + }, + "health_check_start_period": { + "type": "integer", + "description": "Health check start period in seconds." + }, + "limits_memory": { + "type": "string", + "description": "Memory limit." + }, + "limits_memory_swap": { + "type": "string", + "description": "Memory swap limit." + }, + "limits_memory_swappiness": { + "type": "integer", + "description": "Memory swappiness." + }, + "limits_memory_reservation": { + "type": "string", + "description": "Memory reservation." + }, + "limits_cpus": { + "type": "string", + "description": "CPU limit." + }, + "limits_cpuset": { + "type": "string", + "nullable": true, + "description": "CPU set." + }, + "limits_cpu_shares": { + "type": "integer", + "description": "CPU shares." + }, + "status": { + "type": "string", + "description": "Application status." + }, + "preview_url_template": { + "type": "string", + "description": "Preview URL template." + }, + "destination_type": { + "type": "string", + "description": "Destination type." + }, + "destination_id": { + "type": "integer", + "description": "Destination identifier." + }, + "source_id": { + "type": "integer", + "nullable": true, + "description": "Source identifier." + }, + "private_key_id": { + "type": "integer", + "nullable": true, + "description": "Private key identifier." + }, + "environment_id": { + "type": "integer", + "description": "Environment identifier." + }, + "dockerfile": { + "type": "string", + "nullable": true, + "description": "Dockerfile content. Used for dockerfile build pack." + }, + "dockerfile_location": { + "type": "string", + "description": "Dockerfile location." + }, + "custom_labels": { + "type": "string", + "nullable": true, + "description": "Custom labels." + }, + "dockerfile_target_build": { + "type": "string", + "nullable": true, + "description": "Dockerfile target build." + }, + "manual_webhook_secret_github": { + "type": "string", + "nullable": true, + "description": "Manual webhook secret for GitHub." + }, + "manual_webhook_secret_gitlab": { + "type": "string", + "nullable": true, + "description": "Manual webhook secret for GitLab." + }, + "manual_webhook_secret_bitbucket": { + "type": "string", + "nullable": true, + "description": "Manual webhook secret for Bitbucket." + }, + "manual_webhook_secret_gitea": { + "type": "string", + "nullable": true, + "description": "Manual webhook secret for Gitea." + }, + "docker_compose_location": { + "type": "string", + "description": "Docker compose location." + }, + "docker_compose": { + "type": "string", + "nullable": true, + "description": "Docker compose content. Used for docker compose build pack." + }, + "docker_compose_raw": { + "type": "string", + "nullable": true, + "description": "Docker compose raw content." + }, + "docker_compose_domains": { + "type": "string", + "nullable": true, + "description": "Docker compose domains." + }, + "docker_compose_custom_start_command": { + "type": "string", + "nullable": true, + "description": "Docker compose custom start command." + }, + "docker_compose_custom_build_command": { + "type": "string", + "nullable": true, + "description": "Docker compose custom build command." + }, + "swarm_replicas": { + "type": "integer", + "nullable": true, + "description": "Swarm replicas. Only used for swarm deployments." + }, + "swarm_placement_constraints": { + "type": "string", + "nullable": true, + "description": "Swarm placement constraints. Only used for swarm deployments." + }, + "custom_docker_run_options": { + "type": "string", + "nullable": true, + "description": "Custom docker run options." + }, + "post_deployment_command": { + "type": "string", + "nullable": true, + "description": "Post deployment command." + }, + "post_deployment_command_container": { + "type": "string", + "nullable": true, + "description": "Post deployment command container." + }, + "pre_deployment_command": { + "type": "string", + "nullable": true, + "description": "Pre deployment command." + }, + "pre_deployment_command_container": { + "type": "string", + "nullable": true, + "description": "Pre deployment command container." + }, + "watch_paths": { + "type": "string", + "nullable": true, + "description": "Watch paths." + }, + "custom_healthcheck_found": { + "type": "boolean", + "description": "Custom healthcheck found." + }, + "redirect": { + "type": "string", + "nullable": true, + "description": "How to set redirect with Traefik \/ Caddy. www<->non-www.", + "enum": [ + "www", + "non-www", + "both" + ] + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "The date and time when the application was created." + }, + "updated_at": { + "type": "string", + "format": "date-time", + "description": "The date and time when the application was last updated." + }, + "deleted_at": { + "type": "string", + "format": "date-time", + "nullable": true, + "description": "The date and time when the application was deleted." + }, + "compose_parsing_version": { + "type": "string", + "description": "How Coolify parse the compose file." + }, + "custom_nginx_configuration": { + "type": "string", + "nullable": true, + "description": "Custom Nginx configuration base64 encoded." + } + }, + "type": "object" + }, + "ApplicationDeploymentQueue": { + "description": "Project model", + "properties": { + "id": { + "type": "integer" + }, + "application_id": { + "type": "string" + }, + "deployment_uuid": { + "type": "string" + }, + "pull_request_id": { + "type": "integer" + }, + "force_rebuild": { + "type": "boolean" + }, + "commit": { + "type": "string" + }, + "status": { + "type": "string" + }, + "is_webhook": { + "type": "boolean" + }, + "is_api": { + "type": "boolean" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "logs": { + "type": "string" + }, + "current_process_id": { + "type": "string" + }, + "restart_only": { + "type": "boolean" + }, + "git_type": { + "type": "string" + }, + "server_id": { + "type": "integer" + }, + "application_name": { + "type": "string" + }, + "server_name": { + "type": "string" + }, + "deployment_url": { + "type": "string" + }, + "destination_id": { + "type": "string" + }, + "only_this_server": { + "type": "boolean" + }, + "rollback": { + "type": "boolean" + }, + "commit_message": { + "type": "string" + } + }, + "type": "object" + }, + "Environment": { + "description": "Environment model", + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "project_id": { + "type": "integer" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "type": "object" + }, + "EnvironmentVariable": { + "description": "Environment Variable model", + "properties": { + "id": { + "type": "integer" + }, + "uuid": { + "type": "string" + }, + "application_id": { + "type": "integer" + }, + "service_id": { + "type": "integer" + }, + "database_id": { + "type": "integer" + }, + "is_build_time": { + "type": "boolean" + }, + "is_literal": { + "type": "boolean" + }, + "is_multiline": { + "type": "boolean" + }, + "is_preview": { + "type": "boolean" + }, + "is_shared": { + "type": "boolean" + }, + "is_shown_once": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "value": { + "type": "string" + }, + "real_value": { + "type": "string" + }, + "version": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "type": "object" + }, + "PrivateKey": { + "description": "Private Key model", + "properties": { + "id": { + "type": "integer" + }, + "uuid": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "private_key": { + "type": "string", + "format": "private-key" + }, + "is_git_related": { + "type": "boolean" + }, + "team_id": { + "type": "integer" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + } + }, + "type": "object" + }, + "Project": { + "description": "Project model", + "properties": { + "id": { + "type": "integer" + }, + "uuid": { + "type": "string" + }, + "name": { + "type": "string" + }, + "description": { + "type": "string" + }, + "environments": { + "description": "The environments of the project.", + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/Environment" + } + } + }, + "type": "object" + }, + "Server": { + "description": "Server model", + "properties": { + "id": { + "type": "integer", + "description": "The server ID." + }, + "uuid": { + "type": "string", + "description": "The server UUID." + }, + "name": { + "type": "string", + "description": "The server name." + }, + "description": { + "type": "string", + "description": "The server description." + }, + "ip": { + "type": "string", + "description": "The IP address." + }, + "user": { + "type": "string", + "description": "The user." + }, + "port": { + "type": "integer", + "description": "The port number." + }, + "proxy": { + "type": "object", + "description": "The proxy configuration." + }, + "proxy_type": { + "type": "string", + "enum": [ + "traefik", + "caddy", + "none" + ], + "description": "The proxy type." + }, + "high_disk_usage_notification_sent": { + "type": "boolean", + "description": "The flag to indicate if the high disk usage notification has been sent." + }, + "unreachable_notification_sent": { + "type": "boolean", + "description": "The flag to indicate if the unreachable notification has been sent." + }, + "unreachable_count": { + "type": "integer", + "description": "The unreachable count for your server." + }, + "validation_logs": { + "type": "string", + "description": "The validation logs." + }, + "log_drain_notification_sent": { + "type": "boolean", + "description": "The flag to indicate if the log drain notification has been sent." + }, + "swarm_cluster": { + "type": "string", + "description": "The swarm cluster configuration." + }, + "settings": { + "$ref": "#\/components\/schemas\/ServerSetting" + } + }, + "type": "object" + }, + "ServerSetting": { + "description": "Server Settings model", + "properties": { + "id": { + "type": "integer" + }, + "concurrent_builds": { + "type": "integer" + }, + "dynamic_timeout": { + "type": "integer" + }, + "force_disabled": { + "type": "boolean" + }, + "force_server_cleanup": { + "type": "boolean" + }, + "is_build_server": { + "type": "boolean" + }, + "is_cloudflare_tunnel": { + "type": "boolean" + }, + "is_jump_server": { + "type": "boolean" + }, + "is_logdrain_axiom_enabled": { + "type": "boolean" + }, + "is_logdrain_custom_enabled": { + "type": "boolean" + }, + "is_logdrain_highlight_enabled": { + "type": "boolean" + }, + "is_logdrain_newrelic_enabled": { + "type": "boolean" + }, + "is_metrics_enabled": { + "type": "boolean" + }, + "is_reachable": { + "type": "boolean" + }, + "is_sentinel_enabled": { + "type": "boolean" + }, + "is_swarm_manager": { + "type": "boolean" + }, + "is_swarm_worker": { + "type": "boolean" + }, + "is_usable": { + "type": "boolean" + }, + "logdrain_axiom_api_key": { + "type": "string" + }, + "logdrain_axiom_dataset_name": { + "type": "string" + }, + "logdrain_custom_config": { + "type": "string" + }, + "logdrain_custom_config_parser": { + "type": "string" + }, + "logdrain_highlight_project_id": { + "type": "string" + }, + "logdrain_newrelic_base_uri": { + "type": "string" + }, + "logdrain_newrelic_license_key": { + "type": "string" + }, + "sentinel_metrics_history_days": { + "type": "integer" + }, + "sentinel_metrics_refresh_rate_seconds": { + "type": "integer" + }, + "sentinel_token": { + "type": "string" + }, + "docker_cleanup_frequency": { + "type": "string" + }, + "docker_cleanup_threshold": { + "type": "integer" + }, + "server_id": { + "type": "integer" + }, + "wildcard_domain": { + "type": "string" + }, + "created_at": { + "type": "string" + }, + "updated_at": { + "type": "string" + }, + "delete_unused_volumes": { + "type": "boolean", + "description": "The flag to indicate if the unused volumes should be deleted." + }, + "delete_unused_networks": { + "type": "boolean", + "description": "The flag to indicate if the unused networks should be deleted." + } + }, + "type": "object" + }, + "Service": { + "description": "Service model", + "properties": { + "id": { + "type": "integer", + "description": "The unique identifier of the service. Only used for database identification." + }, + "uuid": { + "type": "string", + "description": "The unique identifier of the service." + }, + "name": { + "type": "string", + "description": "The name of the service." + }, + "environment_id": { + "type": "integer", + "description": "The unique identifier of the environment where the service is attached to." + }, + "server_id": { + "type": "integer", + "description": "The unique identifier of the server where the service is running." + }, + "description": { + "type": "string", + "description": "The description of the service." + }, + "docker_compose_raw": { + "type": "string", + "description": "The raw docker-compose.yml file of the service." + }, + "docker_compose": { + "type": "string", + "description": "The docker-compose.yml file that is parsed and modified by Coolify." + }, + "destination_type": { + "type": "string", + "description": "Destination type." + }, + "destination_id": { + "type": "integer", + "description": "The unique identifier of the destination where the service is running." + }, + "connect_to_docker_network": { + "type": "boolean", + "description": "The flag to connect the service to the predefined Docker network." + }, + "is_container_label_escape_enabled": { + "type": "boolean", + "description": "The flag to enable the container label escape." + }, + "is_container_label_readonly_enabled": { + "type": "boolean", + "description": "The flag to enable the container label readonly." + }, + "config_hash": { + "type": "string", + "description": "The hash of the service configuration." + }, + "service_type": { + "type": "string", + "description": "The type of the service." + }, + "created_at": { + "type": "string", + "description": "The date and time when the service was created." + }, + "updated_at": { + "type": "string", + "description": "The date and time when the service was last updated." + }, + "deleted_at": { + "type": "string", + "description": "The date and time when the service was deleted." + } + }, + "type": "object" + }, + "Team": { + "description": "Team model", + "properties": { + "id": { + "type": "integer", + "description": "The unique identifier of the team." + }, + "name": { + "type": "string", + "description": "The name of the team." + }, + "description": { + "type": "string", + "description": "The description of the team." + }, + "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": { + "description": "The members of the team.", + "type": "array", + "items": { + "$ref": "#\/components\/schemas\/User" + } + } + }, + "type": "object" + }, + "User": { + "description": "User model", + "properties": { + "id": { + "type": "integer", + "description": "The user identifier in the database." + }, + "name": { + "type": "string", + "description": "The user name." + }, + "email": { + "type": "string", + "description": "The user email." + }, + "email_verified_at": { + "type": "string", + "description": "The date when the user email was verified." + }, + "created_at": { + "type": "string", + "description": "The date when the user was created." + }, + "updated_at": { + "type": "string", + "description": "The date when the user was updated." + }, + "two_factor_confirmed_at": { + "type": "string", + "description": "The date when the user two factor was confirmed." + }, + "force_password_reset": { + "type": "boolean", + "description": "The flag to force the user to reset the password." + }, + "marketing_emails": { + "type": "boolean", + "description": "The flag to receive marketing emails." + } + }, + "type": "object" + } + }, + "responses": { + "400": { + "description": "Invalid token.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Invalid token." + } + }, + "type": "object" + } + } + } + }, + "401": { + "description": "Unauthenticated.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Unauthenticated." + } + }, + "type": "object" + } + } + } + }, + "404": { + "description": "Resource not found.", + "content": { + "application\/json": { + "schema": { + "properties": { + "message": { + "type": "string", + "example": "Resource not found." + } + }, + "type": "object" + } + } + } + } + }, + "securitySchemes": { + "bearerAuth": { + "type": "http", + "description": "Go to `Keys & Tokens` \/ `API tokens` and create a new token. Use the token as the bearer token.", + "scheme": "bearer" + } + } + }, + "tags": [ + { + "name": "Applications", + "description": "Applications" + }, + { + "name": "Databases", + "description": "Databases" + }, + { + "name": "Deployments", + "description": "Deployments" + }, + { + "name": "Projects", + "description": "Projects" + }, + { + "name": "Resources", + "description": "Resources" + }, + { + "name": "Private Keys", + "description": "Private Keys" + }, + { + "name": "Servers", + "description": "Servers" + }, + { + "name": "Services", + "description": "Services" + }, + { + "name": "Teams", + "description": "Teams" + } + ] +} \ No newline at end of file diff --git a/openapi.yaml b/openapi.yaml index d2616e9c6..2a22c730c 100644 --- a/openapi.yaml +++ b/openapi.yaml @@ -1,4 +1,4 @@ -openapi: 3.0.0 +openapi: 3.1.0 info: title: Coolify version: '0.1' @@ -3311,7 +3311,7 @@ paths: type: string responses: '200': - description: 'Project details' + description: 'Environment details' content: application/json: schema: @@ -3467,9 +3467,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/PrivateKey' + $ref: '#/components/schemas/PrivateKey' '401': $ref: '#/components/responses/401' '400': @@ -3579,6 +3577,11 @@ paths: type: boolean example: false description: 'Instant validate.' + proxy_type: + type: string + enum: [traefik, caddy, none] + example: traefik + description: 'The proxy type.' type: object responses: '201': @@ -3668,6 +3671,14 @@ paths: summary: Update description: 'Update Server.' operationId: update-server-by-uuid + parameters: + - + name: uuid + in: path + description: 'Server UUID' + required: true + schema: + type: string requestBody: description: 'Server updated.' required: true @@ -3699,6 +3710,10 @@ paths: instant_validate: type: boolean description: 'Instant validate.' + proxy_type: + type: string + enum: [traefik, caddy, none] + description: 'The proxy type.' type: object responses: '201': @@ -3706,9 +3721,7 @@ paths: content: application/json: schema: - type: array - items: - $ref: '#/components/schemas/Server' + $ref: '#/components/schemas/Server' '401': $ref: '#/components/responses/401' '400': @@ -4759,6 +4772,10 @@ components: compose_parsing_version: type: string description: 'How Coolify parse the compose file.' + custom_nginx_configuration: + type: string + nullable: true + description: 'Custom Nginx configuration base64 encoded.' type: object ApplicationDeploymentQueue: description: 'Project model' @@ -4909,36 +4926,55 @@ components: properties: id: type: integer + description: 'The server ID.' uuid: type: string + description: 'The server UUID.' name: type: string + description: 'The server name.' description: type: string + description: 'The server description.' ip: type: string + description: 'The IP address.' user: type: string + description: 'The user.' port: type: integer + description: 'The port number.' proxy: type: object + description: 'The proxy configuration.' + proxy_type: + type: string + enum: + - traefik + - caddy + - none + description: 'The proxy type.' high_disk_usage_notification_sent: type: boolean + description: 'The flag to indicate if the high disk usage notification has been sent.' unreachable_notification_sent: type: boolean + description: 'The flag to indicate if the unreachable notification has been sent.' unreachable_count: type: integer + description: 'The unreachable count for your server.' validation_logs: type: string + description: 'The validation logs.' log_drain_notification_sent: type: boolean + description: 'The flag to indicate if the log drain notification has been sent.' swarm_cluster: type: string - delete_unused_volumes: - type: boolean - delete_unused_networks: - type: boolean + description: 'The swarm cluster configuration.' + settings: + $ref: '#/components/schemas/ServerSetting' type: object ServerSetting: description: 'Server Settings model' @@ -5011,6 +5047,12 @@ components: type: string updated_at: type: string + delete_unused_volumes: + type: boolean + description: 'The flag to indicate if the unused volumes should be deleted.' + delete_unused_networks: + type: boolean + description: 'The flag to indicate if the unused networks should be deleted.' type: object Service: description: 'Service model' @@ -5136,6 +5178,9 @@ components: 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.' @@ -5157,6 +5202,9 @@ components: 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.' diff --git a/other/nightly/docker-compose.prod.yml b/other/nightly/docker-compose.prod.yml index b15a109c3..d86b2336b 100644 --- a/other/nightly/docker-compose.prod.yml +++ b/other/nightly/docker-compose.prod.yml @@ -29,6 +29,7 @@ services: - REDIS_HOST - REDIS_PASSWORD - HORIZON_BALANCE + - HORIZON_MIN_PROCESSES - HORIZON_MAX_PROCESSES - HORIZON_BALANCE_MAX_SHIFT - HORIZON_BALANCE_COOLDOWN @@ -50,29 +51,8 @@ services: - TERMINAL_HOST - TERMINAL_PORT - AUTOUPDATE - - SELF_HOSTED - SSH_MUX_ENABLED - SSH_MUX_PERSIST_TIME - - FEEDBACK_DISCORD_WEBHOOK - - WAITLIST - - SUBSCRIPTION_PROVIDER - - STRIPE_API_KEY - - STRIPE_WEBHOOK_SECRET - - STRIPE_PRICE_ID_BASIC_MONTHLY - - STRIPE_PRICE_ID_BASIC_YEARLY - - STRIPE_PRICE_ID_PRO_MONTHLY - - STRIPE_PRICE_ID_PRO_YEARLY - - STRIPE_PRICE_ID_ULTIMATE_MONTHLY - - STRIPE_PRICE_ID_ULTIMATE_YEARLY - - STRIPE_PRICE_ID_DYNAMIC_MONTHLY - - STRIPE_PRICE_ID_DYNAMIC_YEARLY - - STRIPE_PRICE_ID_BASIC_MONTHLY_OLD - - STRIPE_PRICE_ID_BASIC_YEARLY_OLD - - STRIPE_PRICE_ID_PRO_MONTHLY_OLD - - STRIPE_PRICE_ID_PRO_YEARLY_OLD - - STRIPE_PRICE_ID_ULTIMATE_MONTHLY_OLD - - STRIPE_PRICE_ID_ULTIMATE_YEARLY_OLD - - STRIPE_EXCLUDED_PLANS ports: - "${APP_PORT:-8000}:80" expose: @@ -113,7 +93,7 @@ services: retries: 10 timeout: 2s soketi: - image: 'ghcr.io/coollabsio/coolify-realtime:1.0.3' + image: 'ghcr.io/coollabsio/coolify-realtime:1.0.4' ports: - "${SOKETI_PORT:-6001}:6001" - "6002:6002" diff --git a/other/nightly/install.sh b/other/nightly/install.sh index e005b7493..4a03a5c98 100755 --- a/other/nightly/install.sh +++ b/other/nightly/install.sh @@ -5,11 +5,11 @@ set -e # Exit immediately if a command exits with a non-zero status ## $1 could be empty, so we need to disable this check #set -u # Treat unset variables as an error and exit set -o pipefail # Cause a pipeline to return the status of the last command that exited with a non-zero status -CDN="https://cdn.coollabs.io/coolify" +CDN="https://cdn.coollabs.io/coolify-nightly" DATE=$(date +"%Y%m%d-%H%M%S") VERSION="1.6" -DOCKER_VERSION="26.0" +DOCKER_VERSION="27.0" # TODO: Ask for a user CURRENT_USER=$USER @@ -185,11 +185,51 @@ elif [ -x "$(command -v service)" ]; then SSH_DETECTED=true fi fi + + if [ "$SSH_DETECTED" = "false" ]; then - echo "###############################################################################" - echo "WARNING: Could not detect if OpenSSH server is installed and running - this does not mean that it is not installed, just that we could not detect it." - echo -e "Please make sure it is set, otherwise Coolify cannot connect to the host system. \n" - echo "###############################################################################" + echo " - OpenSSH server not detected. Installing OpenSSH server." + case "$OS_TYPE" in + arch) + pacman -Sy --noconfirm openssh >/dev/null + systemctl enable sshd >/dev/null 2>&1 + systemctl start sshd >/dev/null 2>&1 + ;; + alpine) + apk add openssh >/dev/null + rc-update add sshd default >/dev/null 2>&1 + service sshd start >/dev/null 2>&1 + ;; + ubuntu | debian | raspbian) + apt-get update -y >/dev/null + apt-get install -y openssh-server >/dev/null + systemctl enable ssh >/dev/null 2>&1 + systemctl start ssh >/dev/null 2>&1 + ;; + centos | fedora | rhel | ol | rocky | almalinux | amzn) + if [ "$OS_TYPE" = "amzn" ]; then + dnf install -y openssh-server >/dev/null + else + dnf install -y openssh-server >/dev/null + fi + systemctl enable sshd >/dev/null 2>&1 + systemctl start sshd >/dev/null 2>&1 + ;; + sles | opensuse-leap | opensuse-tumbleweed) + zypper install -y openssh >/dev/null + systemctl enable sshd >/dev/null 2>&1 + systemctl start sshd >/dev/null 2>&1 + ;; + *) + echo "###############################################################################" + echo "WARNING: Could not detect and install OpenSSH server - this does not mean that it is not installed or not running, just that we could not detect it." + echo -e "Please make sure it is installed and running, otherwise Coolify cannot connect to the host system. \n" + echo "###############################################################################" + exit 1 + ;; + esac + echo " - OpenSSH server installed successfully." + SSH_DETECTED=true fi # Detect SSH PermitRootLogin @@ -291,7 +331,10 @@ test -s /etc/docker/daemon.json && cp /etc/docker/daemon.json /etc/docker/daemon "log-opts": { "max-size": "10m", "max-file": "3" - } + }, + "default-address-pools": [ + {"base":"10.0.0.0/8","size":24} + ] } EOL cat >/etc/docker/daemon.json.coolify </etc/docker/daemon.json.coolify </dev/null # docker network create --attachable --driver=overlay coolify-overlay 2>/dev/null if [ -f /data/coolify/source/docker-compose.custom.yml ]; then - echo "docker-compose.custom.yml detected." - docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper:${LATEST_HELPER_VERSION:-latest} bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60" + echo "docker-compose.custom.yml detected." >> $LOGFILE + docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper:${LATEST_HELPER_VERSION} bash -c "LATEST_IMAGE=${LATEST_IMAGE} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml -f /data/coolify/source/docker-compose.custom.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60" >> $LOGFILE 2>&1 else - docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper:${LATEST_HELPER_VERSION:-latest} bash -c "LATEST_IMAGE=${1:-} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60" + docker run -v /data/coolify/source:/data/coolify/source -v /var/run/docker.sock:/var/run/docker.sock --rm ghcr.io/coollabsio/coolify-helper:${LATEST_HELPER_VERSION} bash -c "LATEST_IMAGE=${LATEST_IMAGE} docker compose --env-file /data/coolify/source/.env -f /data/coolify/source/docker-compose.yml -f /data/coolify/source/docker-compose.prod.yml up -d --remove-orphans --force-recreate --wait --wait-timeout 60" >> $LOGFILE 2>&1 fi diff --git a/other/nightly/versions.json b/other/nightly/versions.json index c04a3dee6..8b10875d0 100644 --- a/other/nightly/versions.json +++ b/other/nightly/versions.json @@ -1,16 +1,19 @@ { "coolify": { "v4": { - "version": "4.0.0-beta.354" + "version": "4.0.0-beta.367" }, "nightly": { - "version": "4.0.0-beta.355" + "version": "4.0.0-beta.368" }, "helper": { - "version": "1.0.2" + "version": "1.0.3" }, "realtime": { - "version": "1.0.3" + "version": "1.0.4" + }, + "sentinel": { + "version": "0.0.15" } } -} +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index adb1dc65a..22398dcf5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,33 +1,30 @@ { - "name": "html", + "name": "coolify", "lockfileVersion": 3, "requires": true, "packages": { "": { + "name": "coolify", "dependencies": { - "@tailwindcss/forms": "0.5.7", - "@tailwindcss/typography": "0.5.13", + "@tailwindcss/forms": "0.5.9", + "@tailwindcss/typography": "0.5.15", "@xterm/addon-fit": "^0.10.0", "@xterm/xterm": "^5.5.0", - "alpinejs": "3.14.0", - "cookie": "^0.7.0", - "dotenv": "^16.4.5", - "ioredis": "5.4.1", - "node-pty": "^1.0.0", - "tailwindcss-scrollbar": "0.1.0", - "ws": "^8.17.0" + "alpinejs": "3.14.3", + "ioredis": "5.4.1" }, "devDependencies": { - "@vitejs/plugin-vue": "4.5.1", - "autoprefixer": "10.4.19", - "axios": "1.7.5", - "laravel-echo": "1.16.1", - "laravel-vite-plugin": "0.8.1", - "postcss": "8.4.38", + "@vitejs/plugin-vue": "5.2.0", + "autoprefixer": "10.4.20", + "axios": "1.7.7", + "laravel-echo": "1.17.0", + "laravel-vite-plugin": "1.0.6", + "postcss": "8.4.49", "pusher-js": "8.4.0-rc2", - "tailwindcss": "3.4.4", - "vite": "4.5.5", - "vue": "3.4.29" + "tailwind-scrollbar": "^3.1.0", + "tailwindcss": "3.4.14", + "vite": "5.4.11", + "vue": "3.5.12" } }, "node_modules/@alloc/quick-lru": { @@ -41,11 +38,35 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@babel/parser": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.7.tgz", - "integrity": "sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw==", + "node_modules/@babel/helper-string-parser": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.26.0" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -53,14 +74,46 @@ "node": ">=6.0.0" } }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "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==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@esbuild/android-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", - "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -70,13 +123,14 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", - "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", + "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==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -86,13 +140,14 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", - "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "android" @@ -102,13 +157,14 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", - "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -118,13 +174,14 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", - "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "darwin" @@ -134,13 +191,14 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", - "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -150,13 +208,14 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", - "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", + "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==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "freebsd" @@ -166,13 +225,14 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", - "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", "cpu": [ "arm" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -182,13 +242,14 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", - "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -198,13 +259,14 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", - "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -214,13 +276,14 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", - "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", + "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==", "cpu": [ "loong64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -230,13 +293,14 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", - "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", "cpu": [ "mips64el" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -246,13 +310,14 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", - "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", + "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==", "cpu": [ "ppc64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -262,13 +327,14 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", - "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", "cpu": [ "riscv64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -278,13 +344,14 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", - "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", "cpu": [ "s390x" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -294,13 +361,14 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", - "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "linux" @@ -310,13 +378,14 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", - "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "netbsd" @@ -326,13 +395,14 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", - "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "openbsd" @@ -342,13 +412,14 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", - "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", + "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==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "sunos" @@ -358,13 +429,14 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", - "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", "cpu": [ "arm64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -374,13 +446,14 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", - "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", + "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==", "cpu": [ "ia32" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -390,13 +463,14 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", - "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", "cpu": [ "x64" ], "dev": true, + "license": "MIT", "optional": true, "os": [ "win32" @@ -440,9 +514,10 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==", + "license": "MIT" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.18", @@ -490,21 +565,275 @@ "node": ">= 8" } }, + "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", + "integrity": "sha512-CC/ZqFZwlAIbU1wUPisHyV/XRc5RydFrNLtgl3dGYskdwPZdt4HERtKm50a/+DtTlKeCq9IXFEWR+P6blwjqBA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.25.0.tgz", + "integrity": "sha512-/Y76tmLGUJqVBXXCfVS8Q8FJqYGhgH4wl4qTA24E9v/IJM0XvJCGQVSW1QZ4J+VURO9h8YCa28sTFacZXwK7Rg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.25.0.tgz", + "integrity": "sha512-YVT6L3UrKTlC0FpCZd0MGA7NVdp7YNaEqkENbWQ7AOVOqd/7VzyHpgIpc1mIaxRAo1ZsJRH45fq8j4N63I/vvg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.25.0.tgz", + "integrity": "sha512-ZRL+gexs3+ZmmWmGKEU43Bdn67kWnMeWXLFhcVv5Un8FQcx38yulHBA7XR2+KQdYIOtD0yZDWBCudmfj6lQJoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.25.0.tgz", + "integrity": "sha512-xpEIXhiP27EAylEpreCozozsxWQ2TJbOLSivGfXhU4G1TBVEYtUPi2pOZBnvGXHyOdLAUUhPnJzH3ah5cqF01g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.25.0.tgz", + "integrity": "sha512-sC5FsmZGlJv5dOcURrsnIK7ngc3Kirnx3as2XU9uER+zjfyqIjdcMVgzy4cOawhsssqzoAX19qmxgJ8a14Qrqw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.25.0.tgz", + "integrity": "sha512-uD/dbLSs1BEPzg564TpRAQ/YvTnCds2XxyOndAO8nJhaQcqQGFgv/DAVko/ZHap3boCvxnzYMa3mTkV/B/3SWA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.25.0.tgz", + "integrity": "sha512-ZVt/XkrDlQWegDWrwyC3l0OfAF7yeJUF4fq5RMS07YM72BlSfn2fQQ6lPyBNjt+YbczMguPiJoCfaQC2dnflpQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.25.0.tgz", + "integrity": "sha512-qboZ+T0gHAW2kkSDPHxu7quaFaaBlynODXpBVnPxUgvWYaE84xgCKAPEYE+fSMd3Zv5PyFZR+L0tCdYCMAtG0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.25.0.tgz", + "integrity": "sha512-ndWTSEmAaKr88dBuogGH2NZaxe7u2rDoArsejNslugHZ+r44NfWiwjzizVS1nUOHo+n1Z6qV3X60rqE/HlISgw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.25.0.tgz", + "integrity": "sha512-BVSQvVa2v5hKwJSy6X7W1fjDex6yZnNKy3Kx1JGimccHft6HV0THTwNtC2zawtNXKUu+S5CjXslilYdKBAadzA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.25.0.tgz", + "integrity": "sha512-G4hTREQrIdeV0PE2JruzI+vXdRnaK1pg64hemHq2v5fhv8C7WjVaeXc9P5i4Q5UC06d/L+zA0mszYIKl+wY8oA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.25.0.tgz", + "integrity": "sha512-9T/w0kQ+upxdkFL9zPVB6zy9vWW1deA3g8IauJxojN4bnz5FwSsUAD034KpXIVX5j5p/rn6XqumBMxfRkcHapQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.25.0.tgz", + "integrity": "sha512-ThcnU0EcMDn+J4B9LD++OgBYxZusuA7iemIIiz5yzEcFg04VZFzdFjuwPdlURmYPZw+fgVrFzj4CA64jSTG4Ig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.25.0.tgz", + "integrity": "sha512-zx71aY2oQxGxAT1JShfhNG79PnjYhMC6voAjzpu/xmMjDnKNf6Nl/xv7YaB/9SIa9jDYf8RBPWEnjcdlhlv1rQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.25.0.tgz", + "integrity": "sha512-JT8tcjNocMs4CylWY/CxVLnv8e1lE7ff1fi6kbGocWwxDq9pj30IJ28Peb+Y8yiPNSF28oad42ApJB8oUkwGww==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.25.0.tgz", + "integrity": "sha512-dRLjLsO3dNOfSN6tjyVlG+Msm4IiZnGkuZ7G5NmpzwF9oOc582FZG05+UdfTbz5Jd4buK/wMb6UeHFhG18+OEg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.25.0.tgz", + "integrity": "sha512-/RqrIFtLB926frMhZD0a5oDa4eFIbyNEwLLloMTEjmqfwZWXywwVVOVmwTsuyhC9HKkVEZcOOi+KV4U9wmOdlg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@tailwindcss/forms": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.7.tgz", - "integrity": "sha512-QE7X69iQI+ZXwldE+rzasvbJiyV/ju1FGHH0Qn2W3FKbuYtqp8LKcy6iSw79fVUT5/Vvf+0XgLCeYVG+UV6hOw==", + "version": "0.5.9", + "resolved": "https://registry.npmjs.org/@tailwindcss/forms/-/forms-0.5.9.tgz", + "integrity": "sha512-tM4XVr2+UVTxXJzey9Twx48c1gcxFStqn1pQz0tRsX8o3DvxhN5oY5pvyAbUx7VTaZxpej4Zzvc6h+1RJBzpIg==", + "license": "MIT", "dependencies": { "mini-svg-data-uri": "^1.2.3" }, "peerDependencies": { - "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1" + "tailwindcss": ">=3.0.0 || >= 3.0.0-alpha.1 || >= 4.0.0-alpha.20" } }, "node_modules/@tailwindcss/typography": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.13.tgz", - "integrity": "sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==", + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.15.tgz", + "integrity": "sha512-AqhlCXl+8grUz8uqExv5OTtgpjuVIwFTSXTrh8y9/pw6q2ek7fJ+Y8ZEVw7EB2DCcuCOtEjf9w3+J3rzts01uA==", + "license": "MIT", "dependencies": { "lodash.castarray": "^4.4.0", "lodash.isplainobject": "^4.0.6", @@ -512,7 +841,7 @@ "postcss-selector-parser": "6.0.10" }, "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders" + "tailwindcss": ">=3.0.0 || insiders || >=4.0.0-alpha.20" } }, "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { @@ -527,92 +856,108 @@ "node": ">=4" } }, - "node_modules/@vitejs/plugin-vue": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.5.1.tgz", - "integrity": "sha512-DaUzYFr+2UGDG7VSSdShKa9sIWYBa1LL8KC0MNOf2H5LjcTPjob0x8LbkqXWmAtbANJCkpiQTj66UVcQkN2s3g==", + "node_modules/@types/estree": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true, + "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==", + "dev": true, + "license": "MIT", "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.0.0 || >=20.0.0" }, "peerDependencies": { - "vite": "^4.0.0 || ^5.0.0", + "vite": "^5.0.0", "vue": "^3.2.25" } }, "node_modules/@vue/compiler-core": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.29.tgz", - "integrity": "sha512-TFKiRkKKsRCKvg/jTSSKK7mYLJEQdUiUfykbG49rubC9SfDyvT2JrzTReopWlz2MxqeLyxh9UZhvxEIBgAhtrg==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.12.tgz", + "integrity": "sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.24.7", - "@vue/shared": "3.4.29", + "@babel/parser": "^7.25.3", + "@vue/shared": "3.5.12", "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.4.29", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", - "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", - "dev": true + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", + "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "dev": true, + "license": "MIT" }, "node_modules/@vue/compiler-dom": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.29.tgz", - "integrity": "sha512-A6+iZ2fKIEGnfPJejdB7b1FlJzgiD+Y/sxxKwJWg1EbJu6ZPgzaPQQ51ESGNv0CP6jm6Z7/pO6Ia8Ze6IKrX7w==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.12.tgz", + "integrity": "sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==", "dev": true, + "license": "MIT", "dependencies": { - "@vue/compiler-core": "3.4.29", - "@vue/shared": "3.4.29" + "@vue/compiler-core": "3.5.12", + "@vue/shared": "3.5.12" } }, "node_modules/@vue/compiler-dom/node_modules/@vue/shared": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", - "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", - "dev": true + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", + "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "dev": true, + "license": "MIT" }, "node_modules/@vue/compiler-sfc": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.29.tgz", - "integrity": "sha512-zygDcEtn8ZimDlrEQyLUovoWgKQic6aEQqRXce2WXBvSeHbEbcAsXyCk9oG33ZkyWH4sl9D3tkYc1idoOkdqZQ==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.12.tgz", + "integrity": "sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==", "dev": true, + "license": "MIT", "dependencies": { - "@babel/parser": "^7.24.7", - "@vue/compiler-core": "3.4.29", - "@vue/compiler-dom": "3.4.29", - "@vue/compiler-ssr": "3.4.29", - "@vue/shared": "3.4.29", + "@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", "estree-walker": "^2.0.2", - "magic-string": "^0.30.10", - "postcss": "^8.4.38", + "magic-string": "^0.30.11", + "postcss": "^8.4.47", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-sfc/node_modules/@vue/shared": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", - "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", - "dev": true + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", + "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "dev": true, + "license": "MIT" }, "node_modules/@vue/compiler-ssr": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.29.tgz", - "integrity": "sha512-rFbwCmxJ16tDp3N8XCx5xSQzjhidYjXllvEcqX/lopkoznlNPz3jyy0WGJCyhAaVQK677WWFt3YO/WUEkMMUFQ==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.12.tgz", + "integrity": "sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==", "dev": true, + "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.4.29", - "@vue/shared": "3.4.29" + "@vue/compiler-dom": "3.5.12", + "@vue/shared": "3.5.12" } }, "node_modules/@vue/compiler-ssr/node_modules/@vue/shared": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", - "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", - "dev": true + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", + "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "dev": true, + "license": "MIT" }, "node_modules/@vue/reactivity": { "version": "3.1.5", @@ -623,75 +968,83 @@ } }, "node_modules/@vue/runtime-core": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.29.tgz", - "integrity": "sha512-s8fmX3YVR/Rk5ig0ic0NuzTNjK2M7iLuVSZyMmCzN/+Mjuqqif1JasCtEtmtoJWF32pAtUjyuT2ljNKNLeOmnQ==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.12.tgz", + "integrity": "sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==", "dev": true, + "license": "MIT", "dependencies": { - "@vue/reactivity": "3.4.29", - "@vue/shared": "3.4.29" + "@vue/reactivity": "3.5.12", + "@vue/shared": "3.5.12" } }, "node_modules/@vue/runtime-core/node_modules/@vue/reactivity": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.29.tgz", - "integrity": "sha512-w8+KV+mb1a8ornnGQitnMdLfE0kXmteaxLdccm2XwdFxXst4q/Z7SEboCV5SqJNpZbKFeaRBBJBhW24aJyGINg==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.12.tgz", + "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==", "dev": true, + "license": "MIT", "dependencies": { - "@vue/shared": "3.4.29" + "@vue/shared": "3.5.12" } }, "node_modules/@vue/runtime-core/node_modules/@vue/shared": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", - "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", - "dev": true + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", + "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "dev": true, + "license": "MIT" }, "node_modules/@vue/runtime-dom": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.29.tgz", - "integrity": "sha512-gI10atCrtOLf/2MPPMM+dpz3NGulo9ZZR9d1dWo4fYvm+xkfvRrw1ZmJ7mkWtiJVXSsdmPbcK1p5dZzOCKDN0g==", + "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==", "dev": true, + "license": "MIT", "dependencies": { - "@vue/reactivity": "3.4.29", - "@vue/runtime-core": "3.4.29", - "@vue/shared": "3.4.29", + "@vue/reactivity": "3.5.12", + "@vue/runtime-core": "3.5.12", + "@vue/shared": "3.5.12", "csstype": "^3.1.3" } }, "node_modules/@vue/runtime-dom/node_modules/@vue/reactivity": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.29.tgz", - "integrity": "sha512-w8+KV+mb1a8ornnGQitnMdLfE0kXmteaxLdccm2XwdFxXst4q/Z7SEboCV5SqJNpZbKFeaRBBJBhW24aJyGINg==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.12.tgz", + "integrity": "sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==", "dev": true, + "license": "MIT", "dependencies": { - "@vue/shared": "3.4.29" + "@vue/shared": "3.5.12" } }, "node_modules/@vue/runtime-dom/node_modules/@vue/shared": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", - "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", - "dev": true + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", + "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "dev": true, + "license": "MIT" }, "node_modules/@vue/server-renderer": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.29.tgz", - "integrity": "sha512-HMLCmPI2j/k8PVkSBysrA2RxcxC5DgBiCdj7n7H2QtR8bQQPqKAe8qoaxLcInzouBmzwJ+J0x20ygN/B5mYBng==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.12.tgz", + "integrity": "sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==", "dev": true, + "license": "MIT", "dependencies": { - "@vue/compiler-ssr": "3.4.29", - "@vue/shared": "3.4.29" + "@vue/compiler-ssr": "3.5.12", + "@vue/shared": "3.5.12" }, "peerDependencies": { - "vue": "3.4.29" + "vue": "3.5.12" } }, "node_modules/@vue/server-renderer/node_modules/@vue/shared": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", - "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", - "dev": true + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", + "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "dev": true, + "license": "MIT" }, "node_modules/@vue/shared": { "version": "3.1.5", @@ -712,9 +1065,10 @@ "integrity": "sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==" }, "node_modules/alpinejs": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.0.tgz", - "integrity": "sha512-YCWF95PMJqePe9ll6KMyDt/nLhh2R7RhqBf4loEmLzIskcHque4Br/9UgAa6cw13H0Cm3FM9e1hzDwP5z5wlDA==", + "version": "3.14.3", + "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.14.3.tgz", + "integrity": "sha512-cL8JBEDAm4UeVjTN5QnFl8QgMGUwxFn1GvQvu3RtfAHUrAPRahGihrsWpKnEK9L0QMqsAPk/R8MylMWKHaK33A==", + "license": "MIT", "dependencies": { "@vue/reactivity": "~3.1.1" } @@ -748,9 +1102,9 @@ "dev": true }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "dev": true, "funding": [ { @@ -766,12 +1120,13 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -785,9 +1140,9 @@ } }, "node_modules/axios": { - "version": "1.7.5", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.5.tgz", - "integrity": "sha512-fZu86yCo+svH3uqJ/yTdQ0QHpQu5oL+/QE+QPSv6BZSkDAoky9vytxp7u5qk83OJFS3kEBcesWni9WTZAv3tSw==", + "version": "1.7.7", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz", + "integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==", "dev": true, "license": "MIT", "dependencies": { @@ -830,9 +1185,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "dev": true, "funding": [ { @@ -848,11 +1203,12 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" }, "bin": { "browserslist": "cli.js" @@ -870,9 +1226,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001600", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz", - "integrity": "sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ==", + "version": "1.0.30001680", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001680.tgz", + "integrity": "sha512-rPQy70G6AGUMnbwS1z6Xg+RkHYPAi18ihs47GH0jcxIG7wArmPgY3XbS2sRdBbxJljp3thdT8BIqv9ccCypiPA==", "dev": true, "funding": [ { @@ -887,7 +1243,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chokidar": { "version": "3.5.3", @@ -959,14 +1316,6 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, - "node_modules/cookie": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.0.tgz", - "integrity": "sha512-qCf+V4dtlNhSRXGAZatc1TasyFO6GjohcOul807YOb5ik3+kQSnb4d7iajeCL8QHaJ4uZEjCgiCJerKXwdRVlQ==", - "engines": { - "node": ">= 0.6" - } - }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -982,7 +1331,8 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/debug": { "version": "4.3.4", @@ -1027,29 +1377,19 @@ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, - "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, "node_modules/electron-to-chromium": { - "version": "1.4.692", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.692.tgz", - "integrity": "sha512-d5rZRka9n2Y3MkWRN74IoAsxR0HK3yaAt7T50e3iT9VZmCCQDT3geXUO5ZRMhDToa1pkCeQXuNo+0g+NfDOVPA==", - "dev": true + "version": "1.5.55", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.55.tgz", + "integrity": "sha512-6maZ2ASDOTBtjt9FhqYPRnbvKU5tjG0IN9SztUOWYw2AzNDNpKJYLJmlK0/En4Hs/aiWnB+JZ+gW19PIGszgKg==", + "dev": true, + "license": "ISC" }, "node_modules/entities": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.12" }, @@ -1058,11 +1398,12 @@ } }, "node_modules/esbuild": { - "version": "0.18.20", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", - "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", "dev": true, "hasInstallScript": true, + "license": "MIT", "bin": { "esbuild": "bin/esbuild" }, @@ -1070,35 +1411,37 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.18.20", - "@esbuild/android-arm64": "0.18.20", - "@esbuild/android-x64": "0.18.20", - "@esbuild/darwin-arm64": "0.18.20", - "@esbuild/darwin-x64": "0.18.20", - "@esbuild/freebsd-arm64": "0.18.20", - "@esbuild/freebsd-x64": "0.18.20", - "@esbuild/linux-arm": "0.18.20", - "@esbuild/linux-arm64": "0.18.20", - "@esbuild/linux-ia32": "0.18.20", - "@esbuild/linux-loong64": "0.18.20", - "@esbuild/linux-mips64el": "0.18.20", - "@esbuild/linux-ppc64": "0.18.20", - "@esbuild/linux-riscv64": "0.18.20", - "@esbuild/linux-s390x": "0.18.20", - "@esbuild/linux-x64": "0.18.20", - "@esbuild/netbsd-x64": "0.18.20", - "@esbuild/openbsd-x64": "0.18.20", - "@esbuild/sunos-x64": "0.18.20", - "@esbuild/win32-arm64": "0.18.20", - "@esbuild/win32-ia32": "0.18.20", - "@esbuild/win32-x64": "0.18.20" + "@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" } }, "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "dev": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -1107,7 +1450,8 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.2", @@ -1368,28 +1712,33 @@ } }, "node_modules/laravel-echo": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.16.1.tgz", - "integrity": "sha512-++Ylb6M3ariC9Rk5WE5gZjj6wcEV5kvLF8b+geJ5/rRIfdoOA+eG6b9qJPrarMD9rY28Apx+l3eelIrCc2skVg==", + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/laravel-echo/-/laravel-echo-1.17.0.tgz", + "integrity": "sha512-uf+BVZMkXc7+pzxS2dG5v1P+MT3yWS+/9oDSJUcQ4KqnDKLYfM1lc7yUmnxvLtwPksGuQJv6XBtzvWLHSEheNQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10" } }, "node_modules/laravel-vite-plugin": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/laravel-vite-plugin/-/laravel-vite-plugin-0.8.1.tgz", - "integrity": "sha512-fxzUDjOA37kOsYq8dP+3oPIlw8/kJVXwu0hOXLun82R1LpV02shGeWGYKx2lbpKffL5I0sfPPjfqbYxuqBluAA==", + "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==", "dev": true, + "license": "MIT", "dependencies": { "picocolors": "^1.0.0", - "vite-plugin-full-reload": "^1.0.5" + "vite-plugin-full-reload": "^1.1.0" + }, + "bin": { + "clean-orphaned-assets": "bin/clean.js" }, "engines": { - "node": ">=14" + "node": "^18.0.0 || >=20.0.0" }, "peerDependencies": { - "vite": "^3.0.0 || ^4.0.0" + "vite": "^5.0.0" } }, "node_modules/lilconfig": { @@ -1431,12 +1780,13 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, "node_modules/magic-string": { - "version": "0.30.10", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", - "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "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==", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/merge2": { @@ -1514,11 +1864,6 @@ "thenify-all": "^1.0.0" } }, - "node_modules/nan": { - "version": "2.20.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", - "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==" - }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -1536,20 +1881,12 @@ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/node-pty": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-1.0.0.tgz", - "integrity": "sha512-wtBMWWS7dFZm/VgqElrTvtfMq4GzJ6+edFI0Y0zyzygUSZMgZdraDUMUhCIvkjhJjme15qWmbyJbtAx4ot4uZA==", - "hasInstallScript": true, - "dependencies": { - "nan": "^2.17.0" - } - }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", - "dev": true + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", + "dev": true, + "license": "MIT" }, "node_modules/normalize-path": { "version": "3.0.0", @@ -1606,9 +1943,10 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "node_modules/picocolors": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", - "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" }, "node_modules/picomatch": { "version": "2.3.1", @@ -1638,9 +1976,9 @@ } }, "node_modules/postcss": { - "version": "8.4.38", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", - "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "version": "8.4.49", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz", + "integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==", "funding": [ { "type": "opencollective", @@ -1655,10 +1993,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { "nanoid": "^3.3.7", - "picocolors": "^1.0.0", - "source-map-js": "^1.2.0" + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" }, "engines": { "node": "^10 || ^12 || >=14" @@ -1859,18 +2198,40 @@ } }, "node_modules/rollup": { - "version": "3.29.5", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.5.tgz", - "integrity": "sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==", + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.25.0.tgz", + "integrity": "sha512-uVbClXmR6wvx5R1M3Od4utyLUxrmOcEm3pAtMphn73Apq19PDtHpgZoEvqH2YnnaNUuvKmg2DgRd2Sqv+odyqg==", "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.6" + }, "bin": { "rollup": "dist/bin/rollup" }, "engines": { - "node": ">=14.18.0", + "node": ">=18.0.0", "npm": ">=8.0.0" }, "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.25.0", + "@rollup/rollup-android-arm64": "4.25.0", + "@rollup/rollup-darwin-arm64": "4.25.0", + "@rollup/rollup-darwin-x64": "4.25.0", + "@rollup/rollup-freebsd-arm64": "4.25.0", + "@rollup/rollup-freebsd-x64": "4.25.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.25.0", + "@rollup/rollup-linux-arm-musleabihf": "4.25.0", + "@rollup/rollup-linux-arm64-gnu": "4.25.0", + "@rollup/rollup-linux-arm64-musl": "4.25.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.25.0", + "@rollup/rollup-linux-riscv64-gnu": "4.25.0", + "@rollup/rollup-linux-s390x-gnu": "4.25.0", + "@rollup/rollup-linux-x64-gnu": "4.25.0", + "@rollup/rollup-linux-x64-musl": "4.25.0", + "@rollup/rollup-win32-arm64-msvc": "4.25.0", + "@rollup/rollup-win32-ia32-msvc": "4.25.0", + "@rollup/rollup-win32-x64-msvc": "4.25.0", "fsevents": "~2.3.2" } }, @@ -1916,9 +2277,10 @@ } }, "node_modules/source-map-js": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", - "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -1960,10 +2322,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tailwind-scrollbar": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tailwind-scrollbar/-/tailwind-scrollbar-3.1.0.tgz", + "integrity": "sha512-pmrtDIZeHyu2idTejfV59SbaJyvp1VRjYxAjZBH0jnyrPRo6HL1kD5Glz8VPagasqr6oAx6M05+Tuw429Z8jxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.13.0" + }, + "peerDependencies": { + "tailwindcss": "3.x" + } + }, "node_modules/tailwindcss": { - "version": "3.4.4", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.4.tgz", - "integrity": "sha512-ZoyXOdJjISB7/BcLTR6SEsLgKtDStYyYZVLsUtWChO4Ps20CBad7lfJKVDiejocV4ME1hLmyY0WJE3hSDcmQ2A==", + "version": "3.4.14", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.14.tgz", + "integrity": "sha512-IcSvOcTRcUtQQ7ILQL5quRDg7Xs93PdJEk1ZLbhhvJc7uj/OAhYOnruEiwnGgBvUtaUAJ8/mhSw1o8L2jCiENA==", + "license": "MIT", "dependencies": { "@alloc/quick-lru": "^5.2.0", "arg": "^5.0.2", @@ -1996,14 +2372,6 @@ "node": ">=14.0.0" } }, - "node_modules/tailwindcss-scrollbar": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/tailwindcss-scrollbar/-/tailwindcss-scrollbar-0.1.0.tgz", - "integrity": "sha512-egipxw4ooQDh94x02XQpPck0P0sfwazwoUGfA9SedPATIuYDR+6qe8d31Gl7YsSMRiOKDkkqfI0kBvEw9lT/Hg==", - "peerDependencies": { - "tailwindcss": ">= 2.x.x" - } - }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -2046,9 +2414,9 @@ "dev": true }, "node_modules/update-browserslist-db": { - "version": "1.0.13", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", - "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "dev": true, "funding": [ { @@ -2064,9 +2432,10 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "dependencies": { - "escalade": "^3.1.1", - "picocolors": "^1.0.0" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -2081,32 +2450,34 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "node_modules/vite": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/vite/-/vite-4.5.5.tgz", - "integrity": "sha512-ifW3Lb2sMdX+WU91s3R0FyQlAyLxOzCSCP37ujw0+r5POeHPwe6udWVIElKQq8gk3t7b8rkmvqC6IHBpCff4GQ==", + "version": "5.4.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.11.tgz", + "integrity": "sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==", "dev": true, + "license": "MIT", "dependencies": { - "esbuild": "^0.18.10", - "postcss": "^8.4.27", - "rollup": "^3.27.1" + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^18.0.0 || >=20.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "fsevents": "~2.3.2" + "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": ">= 14", + "@types/node": "^18.0.0 || >=20.0.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", + "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.4.0" @@ -2124,6 +2495,9 @@ "sass": { "optional": true }, + "sass-embedded": { + "optional": true + }, "stylus": { "optional": true }, @@ -2146,16 +2520,17 @@ } }, "node_modules/vue": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.29.tgz", - "integrity": "sha512-8QUYfRcYzNlYuzKPfge1UWC6nF9ym0lx7mpGVPJYNhddxEf3DD0+kU07NTL0sXuiT2HuJuKr/iEO8WvXvT0RSQ==", + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.12.tgz", + "integrity": "sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==", "dev": true, + "license": "MIT", "dependencies": { - "@vue/compiler-dom": "3.4.29", - "@vue/compiler-sfc": "3.4.29", - "@vue/runtime-dom": "3.4.29", - "@vue/server-renderer": "3.4.29", - "@vue/shared": "3.4.29" + "@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" }, "peerDependencies": { "typescript": "*" @@ -2167,36 +2542,17 @@ } }, "node_modules/vue/node_modules/@vue/shared": { - "version": "3.4.29", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.29.tgz", - "integrity": "sha512-hQ2gAQcBO/CDpC82DCrinJNgOHI2v+FA7BDW4lMSPeBpQ7sRe2OLHWe5cph1s7D8DUQAwRt18dBDfJJ220APEA==", - "dev": true + "version": "3.5.12", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.12.tgz", + "integrity": "sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==", + "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/ws": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", - "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/yaml": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", diff --git a/package.json b/package.json index 29f8f1a37..a174d73c7 100644 --- a/package.json +++ b/package.json @@ -1,4 +1,5 @@ { + "name": "coolify", "private": true, "type": "module", "scripts": { @@ -6,28 +7,24 @@ "build": "vite build" }, "devDependencies": { - "@vitejs/plugin-vue": "4.5.1", - "autoprefixer": "10.4.19", - "axios": "1.7.5", - "laravel-echo": "1.16.1", - "laravel-vite-plugin": "0.8.1", - "postcss": "8.4.38", + "@vitejs/plugin-vue": "5.2.0", + "autoprefixer": "10.4.20", + "axios": "1.7.7", + "laravel-echo": "1.17.0", + "laravel-vite-plugin": "1.0.6", + "postcss": "8.4.49", "pusher-js": "8.4.0-rc2", - "tailwindcss": "3.4.4", - "vite": "4.5.5", - "vue": "3.4.29" + "tailwind-scrollbar": "^3.1.0", + "tailwindcss": "3.4.14", + "vite": "5.4.11", + "vue": "3.5.12" }, "dependencies": { - "@tailwindcss/forms": "0.5.7", - "@tailwindcss/typography": "0.5.13", + "@tailwindcss/forms": "0.5.9", + "@tailwindcss/typography": "0.5.15", "@xterm/addon-fit": "^0.10.0", "@xterm/xterm": "^5.5.0", - "alpinejs": "3.14.0", - "cookie": "^0.7.0", - "dotenv": "^16.4.5", - "ioredis": "5.4.1", - "node-pty": "^1.0.0", - "tailwindcss-scrollbar": "0.1.0", - "ws": "^8.17.0" + "alpinejs": "3.14.3", + "ioredis": "5.4.1" } -} \ No newline at end of file +} diff --git a/public/.htaccess b/public/.htaccess deleted file mode 100644 index 3aec5e27e..000000000 --- a/public/.htaccess +++ /dev/null @@ -1,21 +0,0 @@ - - - Options -MultiViews -Indexes - - - RewriteEngine On - - # Handle Authorization Header - RewriteCond %{HTTP:Authorization} . - RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}] - - # Redirect Trailing Slashes If Not A Folder... - RewriteCond %{REQUEST_FILENAME} !-d - RewriteCond %{REQUEST_URI} (.+)/$ - RewriteRule ^ %1 [L,R=301] - - # Send Requests To Front Controller... - RewriteCond %{REQUEST_FILENAME} !-d - RewriteCond %{REQUEST_FILENAME} !-f - RewriteRule ^ index.php [L] - diff --git a/public/svgs/apprise.png b/public/svgs/apprise.png new file mode 100644 index 000000000..aa6824bed Binary files /dev/null and b/public/svgs/apprise.png differ diff --git a/public/svgs/beszel.svg b/public/svgs/beszel.svg new file mode 100644 index 000000000..c6836479c --- /dev/null +++ b/public/svgs/beszel.svg @@ -0,0 +1 @@ + diff --git a/public/svgs/convertx.png b/public/svgs/convertx.png new file mode 100644 index 000000000..7f4c41e2e Binary files /dev/null and b/public/svgs/convertx.png differ diff --git a/public/svgs/cyberchef.jpeg b/public/svgs/cyberchef.jpeg new file mode 100644 index 000000000..0405ac395 Binary files /dev/null and b/public/svgs/cyberchef.jpeg differ diff --git a/public/svgs/dashy.png b/public/svgs/dashy.png new file mode 100644 index 000000000..78fabd257 Binary files /dev/null and b/public/svgs/dashy.png differ diff --git a/public/svgs/default.webp b/public/svgs/default.webp new file mode 100644 index 000000000..e47383469 Binary files /dev/null and b/public/svgs/default.webp differ diff --git a/public/svgs/faraday.png b/public/svgs/faraday.png new file mode 100644 index 000000000..0965efe8f Binary files /dev/null and b/public/svgs/faraday.png differ diff --git a/public/svgs/firefox.svg b/public/svgs/firefox.svg new file mode 100644 index 000000000..9a6371d47 --- /dev/null +++ b/public/svgs/firefox.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/hoarder.svg b/public/svgs/hoarder.svg new file mode 100644 index 000000000..6215461d2 --- /dev/null +++ b/public/svgs/hoarder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/hoppscotch.png b/public/svgs/hoppscotch.png new file mode 100644 index 000000000..84c728437 Binary files /dev/null and b/public/svgs/hoppscotch.png differ diff --git a/public/svgs/jupyter.svg b/public/svgs/jupyter.svg new file mode 100644 index 000000000..ab2550874 --- /dev/null +++ b/public/svgs/jupyter.svg @@ -0,0 +1,90 @@ + +Group.svg +Created using Figma 0.90 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/svgs/macos.svg b/public/svgs/macos.svg new file mode 100644 index 000000000..483fa6a17 --- /dev/null +++ b/public/svgs/macos.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/svgs/maybe.svg b/public/svgs/maybe.svg new file mode 100644 index 000000000..9a8aa75cb --- /dev/null +++ b/public/svgs/maybe.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/svgs/mealie.png b/public/svgs/mealie.png new file mode 100644 index 000000000..74a2d7b62 Binary files /dev/null and b/public/svgs/mealie.png differ diff --git a/public/svgs/mosquitto.png b/public/svgs/mosquitto.png deleted file mode 100644 index eb287a7cd..000000000 Binary files a/public/svgs/mosquitto.png and /dev/null differ diff --git a/public/svgs/mosquitto.svg b/public/svgs/mosquitto.svg new file mode 100644 index 000000000..7e7dde5b6 --- /dev/null +++ b/public/svgs/mosquitto.svg @@ -0,0 +1 @@ + image/svg+xml \ No newline at end of file diff --git a/public/svgs/portainer.png b/public/svgs/portainer.png new file mode 100644 index 000000000..2a4010f17 Binary files /dev/null and b/public/svgs/portainer.png differ diff --git a/public/svgs/postiz.svg b/public/svgs/postiz.svg new file mode 100644 index 000000000..6e3baa813 --- /dev/null +++ b/public/svgs/postiz.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/public/svgs/privatebin.svg b/public/svgs/privatebin.svg new file mode 100644 index 000000000..d63c65dbd --- /dev/null +++ b/public/svgs/privatebin.svg @@ -0,0 +1 @@ + diff --git a/public/svgs/readeck.svg b/public/svgs/readeck.svg new file mode 100644 index 000000000..07f6e6157 --- /dev/null +++ b/public/svgs/readeck.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/public/svgs/redlib.svg b/public/svgs/redlib.svg new file mode 100644 index 000000000..16f73b5dd --- /dev/null +++ b/public/svgs/redlib.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/public/svgs/web-check.png b/public/svgs/web-check.png new file mode 100644 index 000000000..5b1384764 Binary files /dev/null and b/public/svgs/web-check.png differ diff --git a/public/svgs/wikijs.svg b/public/svgs/wikijs.svg new file mode 100644 index 000000000..52c4a790b --- /dev/null +++ b/public/svgs/wikijs.svg @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/public/svgs/windows.svg b/public/svgs/windows.svg new file mode 100644 index 000000000..2c7392e9c --- /dev/null +++ b/public/svgs/windows.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/resources/css/app.css b/resources/css/app.css index 73f19de96..1195a3058 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -4,8 +4,6 @@ @tailwind components; @tailwind utilities; - - html, body { @apply h-full bg-neutral-50 text-neutral-800 dark:bg-base dark:text-neutral-400; @@ -32,6 +30,14 @@ body { @apply text-black dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300; } +.input-sticky { + @apply text-black dark:bg-coolgray-100 dark:text-white ring-neutral-200 dark:ring-coolgray-300 focus:ring-2 dark:focus:ring-coolgray-300 focus:ring-neutral-400 block w-full py-1.5 rounded border-0 text-sm ring-1 ring-inset; +} + +.input-sticky-active { + @apply border-2 border-coollabs text-black dark:text-white focus:bg-neutral-200 dark:focus:bg-coolgray-400 focus:border-coollabs; +} + /* Readonly */ .input { @apply dark:read-only:text-neutral-500 dark:read-only:ring-0 dark:read-only:bg-coolgray-100/40 placeholder:text-neutral-300 dark:placeholder:text-neutral-700 read-only:text-neutral-500 read-only:bg-neutral-200; @@ -172,10 +178,6 @@ section { @apply bg-error; } -/* [type='checkbox']:checked { - background-image: url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='black' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e"); -} */ - .menu { @apply flex items-center gap-1; } @@ -197,7 +199,7 @@ section { } .scrollbar { - @apply scrollbar-thumb-coollabs-100 dark:scrollbar-track-coolgray-200 scrollbar-track-neutral-200 scrollbar-w-2; + @apply scrollbar-thumb-coollabs-100 dark:scrollbar-track-coolgray-200 scrollbar-track-neutral-200 scrollbar-thin; } .main { diff --git a/resources/js/bootstrap.js b/resources/js/bootstrap.js deleted file mode 100644 index 3d7608588..000000000 --- a/resources/js/bootstrap.js +++ /dev/null @@ -1,32 +0,0 @@ -/** - * We'll load the axios HTTP library which allows us to easily issue requests - * to our Laravel back-end. This library automatically handles sending the - * CSRF token as a header based on the value of the "XSRF" token cookie. - */ - -// import axios from 'axios'; -// window.axios = axios; - -// window.axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest'; - -/** - * Echo exposes an expressive API for subscribing to channels and listening - * for events that are broadcast by Laravel. Echo and event broadcasting - * allows your team to easily build robust real-time web applications. - */ - -// import Echo from 'laravel-echo'; - -// import Pusher from 'pusher-js'; -// window.Pusher = Pusher; - -// window.Echo = new Echo({ -// broadcaster: 'pusher', -// key: import.meta.env.VITE_PUSHER_APP_KEY, -// cluster: import.meta.env.VITE_PUSHER_APP_CLUSTER ?? 'mt1', -// wsHost: import.meta.env.VITE_PUSHER_HOST ? import.meta.env.VITE_PUSHER_HOST : `ws-${import.meta.env.VITE_PUSHER_APP_CLUSTER}.pusher.com`, -// wsPort: import.meta.env.VITE_PUSHER_PORT ?? 80, -// wssPort: import.meta.env.VITE_PUSHER_PORT ?? 443, -// forceTLS: (import.meta.env.VITE_PUSHER_SCHEME ?? 'https') === 'https', -// enabledTransports: ['ws', 'wss'], -// }); diff --git a/resources/js/terminal.js b/resources/js/terminal.js index 59c9a79a8..e70a8cd1a 100644 --- a/resources/js/terminal.js +++ b/resources/js/terminal.js @@ -60,7 +60,22 @@ export function initializeTerminalComponent() { }; }, + resetTerminal() { + if (this.term) { + this.$wire.dispatch('error', 'Terminal websocket connection lost.'); + this.term.reset(); + this.term.clear(); + this.pendingWrites = 0; + this.paused = false; + this.commandBuffer = ''; + // Force a refresh + this.$nextTick(() => { + this.resizeTerminal(); + this.term.focus(); + }); + } + }, setupTerminal() { const terminalElement = document.getElementById('terminal'); if (terminalElement) { @@ -69,9 +84,15 @@ export function initializeTerminalComponent() { rows: 30, fontFamily: '"Fira Code", courier-new, courier, monospace, "Powerline Extra Symbols"', cursorBlink: true, + rendererType: 'canvas', + convertEol: true, + disableStdin: false }); this.fitAddon = new FitAddon(); this.term.loadAddon(this.fitAddon); + this.$nextTick(() => { + this.resizeTerminal(); + }); } }, @@ -101,12 +122,19 @@ export function initializeTerminalComponent() { `${connectionString.protocol}://${connectionString.host}${connectionString.port}${connectionString.path}` this.socket = new WebSocket(url); + this.socket.onopen = () => { + console.log('[Terminal] WebSocket connection established. Cool cool cool cool cool cool.'); + }; + this.socket.onmessage = this.handleSocketMessage.bind(this); this.socket.onerror = (e) => { - console.error('WebSocket error:', e); + console.error('[Terminal] WebSocket error.'); }; this.socket.onclose = () => { - console.log('WebSocket connection closed'); + console.warn('[Terminal] WebSocket connection closed.'); + this.resetTerminal(); + this.message = '(connection closed)'; + this.terminalActive = false; this.reconnect(); }; } @@ -117,19 +145,18 @@ export function initializeTerminalComponent() { clearInterval(this.reconnectInterval); } this.reconnectInterval = setInterval(() => { - console.log('Attempting to reconnect...'); + console.warn('[Terminal] Attempting to reconnect...'); this.initializeWebSocket(); if (this.socket && this.socket.readyState === WebSocket.OPEN) { - console.log('Reconnected successfully'); + console.log('[Terminal] Reconnected successfully'); clearInterval(this.reconnectInterval); this.reconnectInterval = null; - window.location.reload(); + } }, 2000); }, handleSocketMessage(event) { - this.message = '(connection closed)'; if (event.data === 'pty-ready') { if (!this.term._initialized) { this.term.open(document.getElementById('terminal')); @@ -150,8 +177,17 @@ export function initializeTerminalComponent() { this.term.reset(); this.commandBuffer = ''; } else { - this.pendingWrites++; - this.term.write(event.data, this.flowControlCallback.bind(this)); + try { + this.pendingWrites++; + this.term.write(event.data, (err) => { + if (err) { + console.error('[Terminal] Write error:', err); + } + this.flowControlCallback(); + }); + } catch (error) { + console.error('[Terminal] Write operation failed:', error); + } } }, @@ -173,11 +209,15 @@ export function initializeTerminalComponent() { if (!this.term) return; this.term.onData((data) => { - this.socket.send(JSON.stringify({ message: data })); - if (data === '\r') { - this.commandBuffer = ''; + if (this.socket.readyState === WebSocket.OPEN) { + this.socket.send(JSON.stringify({ message: data })); + if (data === '\r') { + this.commandBuffer = ''; + } else { + this.commandBuffer += data; + } } else { - this.commandBuffer += data; + console.warn('[Terminal] WebSocket not ready, data not sent'); } }); diff --git a/resources/views/auth/forgot-password.blade.php b/resources/views/auth/forgot-password.blade.php index f66b460be..00cf95a44 100644 --- a/resources/views/auth/forgot-password.blade.php +++ b/resources/views/auth/forgot-password.blade.php @@ -18,7 +18,7 @@ @else
Transactional emails are not active on this instance.
See how to set it in our docs, or how to + href="{{ config('constants.urls.docs') }}">docs, or how to manually reset password.
@endif diff --git a/resources/views/components/forms/checkbox.blade.php b/resources/views/components/forms/checkbox.blade.php index c8c4a499f..fb244962d 100644 --- a/resources/views/components/forms/checkbox.blade.php +++ b/resources/views/components/forms/checkbox.blade.php @@ -5,6 +5,7 @@ 'disabled' => false, 'instantSave' => false, 'value' => null, + 'checked' => false, 'hideLabel' => false, 'fullWidth' => false, ]) @@ -14,8 +15,10 @@ 'w-full' => $fullWidth, ])> @if (!$hideLabel) - + @endif diff --git a/resources/views/components/modal-confirmation.blade.php b/resources/views/components/modal-confirmation.blade.php index f0c42a71d..8495417ac 100644 --- a/resources/views/components/modal-confirmation.blade.php +++ b/resources/views/components/modal-confirmation.blade.php @@ -28,7 +28,7 @@ $disableTwoStepConfirmation = data_get(InstanceSettings::get(), 'disable_two_step_confirmation'); @endphp -
+}" @keydown.escape.window="modalOpen = false; resetModal()" + :class="{ 'z-40': modalOpen }" class="relative w-auto h-auto"> @if ($customButton) @if ($buttonFullWidth) @@ -302,7 +302,8 @@ @endif - - + + -

+

+

@error('password')

{{ $message }}

@enderror diff --git a/resources/views/components/modal-input.blade.php b/resources/views/components/modal-input.blade.php index f396f399d..c090037d5 100644 --- a/resources/views/components/modal-input.blade.php +++ b/resources/views/components/modal-input.blade.php @@ -9,7 +9,7 @@ 'closeOutside' => true, ])
+ class="relative w-auto h-auto" wire:ignore> @if ($content)
{{ $content }} @@ -27,7 +27,7 @@ @endif