Merge branch 'next' into feat/manage-db-using-api
This commit is contained in:
		
							
								
								
									
										65
									
								
								.github/workflows/browser-tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										65
									
								
								.github/workflows/browser-tests.yml
									
									
									
									
										vendored
									
									
								
							| @@ -1,65 +0,0 @@ | ||||
| name: Dusk | ||||
| on: | ||||
|   push: | ||||
|     branches: [ "not-existing" ] | ||||
| jobs: | ||||
|   dusk: | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     services: | ||||
|       redis: | ||||
|         image: redis | ||||
|         env: | ||||
|           REDIS_HOST: localhost | ||||
|           REDIS_PORT: 6379 | ||||
|         ports: | ||||
|           - 6379:6379 | ||||
|         options: >- | ||||
|           --health-cmd "redis-cli ping" | ||||
|           --health-interval 10s | ||||
|           --health-timeout 5s | ||||
|           --health-retries 5 | ||||
|  | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - name: Set up PostgreSQL | ||||
|         run: | | ||||
|           sudo systemctl start postgresql | ||||
|           sudo -u postgres psql -c "CREATE DATABASE coolify;" | ||||
|           sudo -u postgres psql -c "CREATE USER coolify WITH PASSWORD 'password';" | ||||
|           sudo -u postgres psql -c "ALTER ROLE coolify SET client_encoding TO 'utf8';" | ||||
|           sudo -u postgres psql -c "ALTER ROLE coolify SET default_transaction_isolation TO 'read committed';" | ||||
|           sudo -u postgres psql -c "ALTER ROLE coolify SET timezone TO 'UTC';" | ||||
|           sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE coolify TO coolify;" | ||||
|       - name: Setup PHP | ||||
|         uses: shivammathur/setup-php@v2 | ||||
|         with: | ||||
|           php-version: '8.2' | ||||
|       - name: Copy .env | ||||
|         run: cp .env.dusk.ci .env | ||||
|       - name: Install Dependencies | ||||
|         run: composer install --no-progress --prefer-dist --optimize-autoloader | ||||
|       - name: Generate key | ||||
|         run: php artisan key:generate | ||||
|       - name: Install Chrome binaries | ||||
|         run: php artisan dusk:chrome-driver --detect | ||||
|       - name: Start Chrome Driver | ||||
|         run: ./vendor/laravel/dusk/bin/chromedriver-linux --port=4444 & | ||||
|       - name: Build assets | ||||
|         run: npm install && npm run build | ||||
|       - name: Run Laravel Server | ||||
|         run: php artisan serve --no-reload & | ||||
|       - name: Execute tests | ||||
|         run: php artisan dusk | ||||
|       - name: Upload Screenshots | ||||
|         if: failure() | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: screenshots | ||||
|           path: tests/Browser/screenshots | ||||
|       - name: Upload Console Logs | ||||
|         if: failure() | ||||
|         uses: actions/upload-artifact@v4 | ||||
|         with: | ||||
|           name: console | ||||
|           path: tests/Browser/console | ||||
| @@ -21,7 +21,7 @@ jobs: | ||||
|  | ||||
|             async function processIssue(issueNumber, isFromPR = false, prBaseBranch = null) { | ||||
|               try { | ||||
|                 if (isFromPR && prBaseBranch !== 'main') { | ||||
|                 if (isFromPR && prBaseBranch !== 'v4.x') { | ||||
|                   return; | ||||
|                 } | ||||
|  | ||||
| @@ -70,7 +70,7 @@ jobs: | ||||
|             if (context.eventName === 'pull_request' || context.eventName === 'pull_request_target') { | ||||
|               const pr = context.payload.pull_request; | ||||
|               await processIssue(pr.number); | ||||
|               if (pr.merged && pr.base.ref === 'main' && pr.body) { | ||||
|               if (pr.merged && pr.base.ref === 'v4.x' && pr.body) { | ||||
|                 const issueReferences = pr.body.match(/#(\d+)/g); | ||||
|                 if (issueReferences) { | ||||
|                   for (const reference of issueReferences) { | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/coolify-helper.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/coolify-helper.yml
									
									
									
									
										vendored
									
									
								
							| @@ -2,7 +2,7 @@ name: Coolify Helper Image | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: [ "main" ] | ||||
|     branches: [ "v4.x" ] | ||||
|     paths: | ||||
|       - .github/workflows/coolify-helper.yml | ||||
|       - docker/coolify-helper/Dockerfile | ||||
|   | ||||
| @@ -2,7 +2,7 @@ name: Production Build (v4) | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: ["main"] | ||||
|     branches: ["v4.x"] | ||||
|     paths-ignore: | ||||
|       - .github/workflows/coolify-helper.yml | ||||
|       - .github/workflows/coolify-helper-next.yml | ||||
|   | ||||
							
								
								
									
										2
									
								
								.github/workflows/coolify-realtime.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/coolify-realtime.yml
									
									
									
									
										vendored
									
									
								
							| @@ -2,7 +2,7 @@ name: Coolify Realtime | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: [ "main" ] | ||||
|     branches: [ "v4.x" ] | ||||
|     paths: | ||||
|       - .github/workflows/coolify-realtime.yml | ||||
|       - docker/coolify-realtime/Dockerfile | ||||
|   | ||||
							
								
								
									
										5
									
								
								.github/workflows/coolify-staging-build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										5
									
								
								.github/workflows/coolify-staging-build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -2,7 +2,10 @@ name: Staging Build | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches-ignore: ["main", "v3"] | ||||
|     branches-ignore: | ||||
|       - v4.x | ||||
|       - v3.x | ||||
|       - '**v5.x**' | ||||
|     paths-ignore: | ||||
|         - .github/workflows/coolify-helper.yml | ||||
|         - .github/workflows/coolify-helper-next.yml | ||||
|   | ||||
							
								
								
									
										4
									
								
								.github/workflows/generate-changelog.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/workflows/generate-changelog.yml
									
									
									
									
										vendored
									
									
								
							| @@ -2,7 +2,7 @@ name: Generate Changelog | ||||
|  | ||||
| on: | ||||
|   push: | ||||
|     branches: [ main ] | ||||
|     branches: [ v4.x ] | ||||
|   workflow_dispatch: | ||||
|  | ||||
| permissions: | ||||
| @@ -33,4 +33,4 @@ jobs: | ||||
|           git config user.email 'github-actions[bot]@users.noreply.github.com' | ||||
|           git add CHANGELOG.md | ||||
|           git commit -m "docs: update changelog" | ||||
|           git push https://${{ secrets.GITHUB_TOKEN }}@github.com/${GITHUB_REPOSITORY}.git main | ||||
|           git push https://${{ secrets.GITHUB_TOKEN }}@github.com/${GITHUB_REPOSITORY}.git v4.x | ||||
|   | ||||
							
								
								
									
										273
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										273
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -2,10 +2,100 @@ | ||||
| 
 | ||||
| All notable changes to this project will be documented in this file. | ||||
| 
 | ||||
| ## [unreleased] | ||||
| ## [4.0.0-beta.417] - 2025-05-07 | ||||
| 
 | ||||
| ### 📚 Documentation | ||||
| 
 | ||||
| - Update changelog | ||||
| 
 | ||||
| ## [4.0.0-beta.416] - 2025-05-05 | ||||
| 
 | ||||
| ### 📚 Documentation | ||||
| 
 | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| 
 | ||||
| ## [4.0.0-beta.415] - 2025-04-29 | ||||
| 
 | ||||
| ### 📚 Documentation | ||||
| 
 | ||||
| - Update changelog | ||||
| 
 | ||||
| ## [4.0.0-beta.413] - 2025-04-28 | ||||
| 
 | ||||
| ### 💼 Other | ||||
| 
 | ||||
| - Adjust Workflows for v5 (#5689) | ||||
| 
 | ||||
| ### 📚 Documentation | ||||
| 
 | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| 
 | ||||
| ### ⚙️ Miscellaneous Tasks | ||||
| 
 | ||||
| - *(workflows)* Adjust workflow for announcement | ||||
| 
 | ||||
| ## [4.0.0-beta.412] - 2025-04-23 | ||||
| 
 | ||||
| ### ⚙️ Miscellaneous Tasks | ||||
| 
 | ||||
| - *(versions)* Update coolify version to 4.0.0-beta.412 and nightly version to 4.0.0-beta.413 in configuration files | ||||
| 
 | ||||
| ## [4.0.0-beta.411] - 2025-04-23 | ||||
| 
 | ||||
| ### 🚀 Features | ||||
| 
 | ||||
| - *(deployment)* Add repository_project_id handling for private GitHub apps and clean up unused Caddy label logic | ||||
| - *(api)* Enhance OpenAPI specifications with token variable and additional key attributes | ||||
| - *(docker)* Add HTTP Basic Authentication support and enhance hostname parsing in Docker run conversion | ||||
| - *(api)* Add HTTP Basic Authentication fields to OpenAPI specifications and enhance PrivateKey model descriptions | ||||
| 
 | ||||
| ### 🐛 Bug Fixes | ||||
| 
 | ||||
| - *(backup-edit)* Conditionally enable S3 checkbox based on available validated S3 storage | ||||
| - *(source)* Update no sources found message for clarity | ||||
| - *(api)* Correct middleware for service update route to ensure proper permissions | ||||
| - *(api)* Handle JSON response in service creation and update methods for improved error handling | ||||
| - Add 201 json code to servers validate api response | ||||
| - *(docker)* Ensure password hashing only occurs when HTTP Basic Authentication is enabled | ||||
| - *(docker)* Enhance hostname and GPU option validation in Docker run to compose conversion | ||||
| 
 | ||||
| ### 🚜 Refactor | ||||
| 
 | ||||
| - *(jobs)* Comment out unused Caddy label handling in ApplicationDeploymentJob and simplify proxy path logic in Server model | ||||
| - *(database)* Simplify database type checks in ServiceDatabase and enhance image validation in Docker helper | ||||
| - *(shared)* Remove unused ray debugging statement from newParser function | ||||
| - *(applications)* Remove redundant error response in create_env method | ||||
| - *(api)* Restructure routes to include versioning and maintain existing feedback endpoint | ||||
| - *(api)* Remove token variable from OpenAPI specifications for clarity | ||||
| - *(environment-variables)* Remove protected variable checks from delete methods for cleaner logic | ||||
| - *(http-basic-auth)* Rename 'http_basic_auth_enable' to 'http_basic_auth_enabled' across application files for consistency | ||||
| - *(docker)* Remove debug statement and enhance hostname handling in Docker run conversion | ||||
| - *(server)* Simplify proxy path logic and remove unnecessary conditions | ||||
| 
 | ||||
| ### 📚 Documentation | ||||
| 
 | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| 
 | ||||
| ### ⚙️ Miscellaneous Tasks | ||||
| 
 | ||||
| - *(versions)* Update coolify version to 4.0.0-beta.411 and nightly version to 4.0.0-beta.412 in configuration files | ||||
| 
 | ||||
| ## [4.0.0-beta.410] - 2025-04-18 | ||||
| 
 | ||||
| ### 🚀 Features | ||||
| 
 | ||||
| - Add HTTP Basic Authentication | ||||
| - *(readme)* Add new sponsors Supadata AI and WZ-IT to the README | ||||
| - *(core)* Enable magic env variables for compose based applications | ||||
| 
 | ||||
| ### 🐛 Bug Fixes | ||||
| 
 | ||||
| - *(application)* Append base directory to git branch URLs for improved path handling | ||||
| - *(templates)* Correct casing of "denokv" to "denoKV" in service templates JSON | ||||
| - *(navbar)* Update error message link to use route for environment variables navigation | ||||
| - Unsend template | ||||
| - Replace ports with expose | ||||
| @@ -19,14 +109,33 @@ All notable changes to this project will be documented in this file. | ||||
| 
 | ||||
| - Update changelog | ||||
| 
 | ||||
| ### ⚙️ Miscellaneous Tasks | ||||
| 
 | ||||
| - *(versions)* Bump coolify version to 4.0.0-beta.410 and update nightly version to 4.0.0-beta.411 in configuration files | ||||
| - *(templates)* Update plausible and clickhouse images to latest versions and remove mail service | ||||
| 
 | ||||
| ## [4.0.0-beta.409] - 2025-04-16 | ||||
| 
 | ||||
| ### 🐛 Bug Fixes | ||||
| 
 | ||||
| - *(parser)* Transform associative array labels into key=value format for better compatibility | ||||
| - *(redis)* Update username and password input handling to clarify database sync requirements | ||||
| - *(source)* Update connected source display to handle cases with no source connected | ||||
| 
 | ||||
| ### 🚜 Refactor | ||||
| 
 | ||||
| - *(source)* Conditionally display connected source and change source options based on private key presence | ||||
| 
 | ||||
| ### ⚙️ Miscellaneous Tasks | ||||
| 
 | ||||
| - *(versions)* Bump coolify version to 4.0.0-beta.409 in configuration files | ||||
| 
 | ||||
| ## [4.0.0-beta.408] - 2025-04-14 | ||||
| 
 | ||||
| ### 🚀 Features | ||||
| 
 | ||||
| - *(OpenApi)* Enhance OpenAPI specifications by adding UUID parameters for application, project, and service updates; improve deployment listing with pagination parameters; update command signature for OpenApi generation | ||||
| - *(subscription)* Enhance subscription management with loading states and Stripe status checks | ||||
| - *(readme)* Add new sponsors Supadata AI and WZ-IT to the README | ||||
| - *(core)* Enable magic env variables for compose based applications | ||||
| 
 | ||||
| ### 🐛 Bug Fixes | ||||
| 
 | ||||
| @@ -36,11 +145,6 @@ All notable changes to this project will be documented in this file. | ||||
| - *(mongodb)* Also apply custom config when SSL is enabled | ||||
| - *(templates)* Correct casing of denoKV references in service templates and YAML files | ||||
| - *(deployment)* Handle missing destination in deployment process to prevent errors | ||||
| - *(parser)* Transform associative array labels into key=value format for better compatibility | ||||
| - *(redis)* Update username and password input handling to clarify database sync requirements | ||||
| - *(source)* Update connected source display to handle cases with no source connected | ||||
| - *(application)* Append base directory to git branch URLs for improved path handling | ||||
| - *(templates)* Correct casing of "denokv" to "denoKV" in service templates JSON | ||||
| 
 | ||||
| ### 💼 Other | ||||
| 
 | ||||
| @@ -52,7 +156,6 @@ All notable changes to this project will be documented in this file. | ||||
| - *(Dockerfile)* Remove service generation command from the build process to streamline Dockerfile and improve build efficiency | ||||
| - *(navbar-delete-team)* Simplify modal confirmation layout and enhance button styling for better user experience | ||||
| - *(Server)* Remove debug logging from isReachableChanged method to clean up code and improve performance | ||||
| - *(source)* Conditionally display connected source and change source options based on private key presence | ||||
| 
 | ||||
| ### 📚 Documentation | ||||
| 
 | ||||
| @@ -65,9 +168,6 @@ All notable changes to this project will be documented in this file. | ||||
| - *(versions)* Update nightly version to 4.0.0-beta.410 | ||||
| - *(pre-commit)* Remove OpenAPI generation command from pre-commit hook | ||||
| - *(versions)* Update realtime version to 1.0.7 and bump dependencies in package.json | ||||
| - *(versions)* Bump coolify version to 4.0.0-beta.409 in configuration files | ||||
| - *(versions)* Bump coolify version to 4.0.0-beta.410 and update nightly version to 4.0.0-beta.411 in configuration files | ||||
| - *(templates)* Update plausible and clickhouse images to latest versions and remove mail service | ||||
| 
 | ||||
| ## [4.0.0-beta.407] - 2025-04-09 | ||||
| 
 | ||||
| @@ -128,6 +228,7 @@ All notable changes to this project will be documented in this file. | ||||
| ### 🚀 Features | ||||
| 
 | ||||
| - *(api)* Update OpenAPI spec for services (#5448) | ||||
| - *(proxy)* Enhance proxy handling and port conflict detection | ||||
| 
 | ||||
| ### 🐛 Bug Fixes | ||||
| 
 | ||||
| @@ -140,6 +241,10 @@ All notable changes to this project will be documented in this file. | ||||
| 
 | ||||
| ### 📚 Documentation | ||||
| 
 | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| @@ -155,15 +260,12 @@ All notable changes to this project will be documented in this file. | ||||
| 
 | ||||
| ### 🚀 Features | ||||
| 
 | ||||
| - *(proxy)* Enhance proxy handling and port conflict detection | ||||
| - *(lang)* Added Azerbaijani language updated turkish language. (#5497) | ||||
| - *(lang)* Added Portuguese from Brazil language (#5500) | ||||
| - *(lang)* Add Indonesian language translations (#5513) | ||||
| 
 | ||||
| ### 🐛 Bug Fixes | ||||
| 
 | ||||
| - *(database)* Custom config for MongoDB (#5471) | ||||
| - *(ui)* Instance Backup settings | ||||
| - *(docs)* Comment out execute for now | ||||
| - *(installation)* Mount the docker config | ||||
| - *(installation)* Path to config file for docker login | ||||
| @@ -173,13 +275,10 @@ All notable changes to this project will be documented in this file. | ||||
| - *(docs)* Contribute service url (#5517) | ||||
| - *(proxy)* Proxy restart does not work on domain | ||||
| - *(ui)* Only show copy button on https | ||||
| - *(database)* Custom config for MongoDB (#5471) | ||||
| 
 | ||||
| ### 📚 Documentation | ||||
| 
 | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| - Update changelog | ||||
| @@ -187,10 +286,9 @@ All notable changes to this project will be documented in this file. | ||||
| 
 | ||||
| ### ⚙️ Miscellaneous Tasks | ||||
| 
 | ||||
| - *(versions)* Bump version to 403 (#5520) | ||||
| - *(versions)* Update coolify version numbers to 4.0.0-beta.403 and 4.0.0-beta.404 | ||||
| - *(service)* Remove unused code in Bugsink service | ||||
| - *(versions)* Update version to 404 | ||||
| - *(versions)* Bump version to 403 (#5520) | ||||
| - *(versions)* Bump version to 404 | ||||
| 
 | ||||
| ## [4.0.0-beta.402] - 2025-04-01 | ||||
| @@ -210,6 +308,7 @@ All notable changes to this project will be documented in this file. | ||||
| - *(DeployController)* Cast 'pr' query parameter to integer | ||||
| - *(deploy)* Validate team ID before deployment | ||||
| - *(wakapi)* Typo in env variables and add some useful variables to wakapi.yaml (#5424) | ||||
| - *(ui)* Instance Backup settings | ||||
| 
 | ||||
| ### 🚜 Refactor | ||||
| 
 | ||||
| @@ -223,6 +322,7 @@ All notable changes to this project will be documented in this file. | ||||
| - *(service)* Add google variables to plausible.yaml (#5429) | ||||
| - *(service)* Update authentik.yaml versions (#5373) | ||||
| - *(core)* Remove redocs | ||||
| - *(versions)* Update coolify version numbers to 4.0.0-beta.403 and 4.0.0-beta.404 | ||||
| 
 | ||||
| ## [4.0.0-beta.401] - 2025-03-28 | ||||
| 
 | ||||
| @@ -293,14 +393,6 @@ All notable changes to this project will be documented in this file. | ||||
| 
 | ||||
| ### 🚀 Features | ||||
| 
 | ||||
| - *(github-source)* Enhance GitHub App configuration with manual and private key support | ||||
| - *(ui)* Improve GitHub repository selection and styling | ||||
| - *(database)* Implement two-step confirmation for database deletion | ||||
| - *(assets)* Add new SVG logo for Coolify | ||||
| - *(install)* Enhance Docker address pool configuration and validation | ||||
| - *(install)* Improve Docker address pool management and service restart logic | ||||
| - *(install)* Add missing env variable to install script | ||||
| - *(LocalFileVolume)* Add binary file detection and update UI logic | ||||
| - *(service)* Neon | ||||
| - *(migration)* Add `ssl_certificates` table and model | ||||
| - *(migration)* Add ssl setting to `standalone_postgresqls` table | ||||
| @@ -342,6 +434,14 @@ All notable changes to this project will be documented in this file. | ||||
| - *(ssl)* Improve Redis and remove modes | ||||
| - Full SSL support for DrangonflyDB | ||||
| - SSL notification | ||||
| - *(github-source)* Enhance GitHub App configuration with manual and private key support | ||||
| - *(ui)* Improve GitHub repository selection and styling | ||||
| - *(database)* Implement two-step confirmation for database deletion | ||||
| - *(assets)* Add new SVG logo for Coolify | ||||
| - *(install)* Enhance Docker address pool configuration and validation | ||||
| - *(install)* Improve Docker address pool management and service restart logic | ||||
| - *(install)* Add missing env variable to install script | ||||
| - *(LocalFileVolume)* Add binary file detection and update UI logic | ||||
| - *(templates)* Change glance for v0.7 | ||||
| - *(templates)* Add Freescout service template | ||||
| - *(service)* Add Evolution API template | ||||
| @@ -359,18 +459,6 @@ All notable changes to this project will be documented in this file. | ||||
| 
 | ||||
| - *(api)* Docker compose based apps creationg through api | ||||
| - *(database)* Improve database type detection for Supabase Postgres images | ||||
| - *(ui)* Correct grammatical error in 404 page | ||||
| - *(seeder)* Update GitHub app name in GithubAppSeeder | ||||
| - *(plane)* Update APP_RELEASE to v0.25.2 in environment configuration | ||||
| - *(domain)* Dispatch refreshStatus event after successful domain update | ||||
| - *(database)* Correct container name generation for service databases | ||||
| - *(database)* Limit container name length for database proxy | ||||
| - *(database)* Handle unsupported database types in StartDatabaseProxy | ||||
| - *(database)* Simplify container name generation in StartDatabaseProxy | ||||
| - *(install)* Handle potential errors in Docker address pool configuration | ||||
| - *(backups)* Retention settings | ||||
| - *(redis)* Set default redis_username for new instances | ||||
| - *(core)* Improve instantSave logic and error handling | ||||
| - *(ssl)* Permission of ssl crt and key inside the container | ||||
| - *(ui)* Make sure file mounts do not showing the encrypted values | ||||
| - *(ssl)* Make default ssl mode require not verify-full as it does not need a ca cert | ||||
| @@ -410,6 +498,18 @@ All notable changes to this project will be documented in this file. | ||||
| - *(ssl)* Add `--tls` arg to DrangflyDB | ||||
| - *(notification)* Always send SSL notifications | ||||
| - *(database)* Change default value of enable_ssl to false for multiple tables | ||||
| - *(ui)* Correct grammatical error in 404 page | ||||
| - *(seeder)* Update GitHub app name in GithubAppSeeder | ||||
| - *(plane)* Update APP_RELEASE to v0.25.2 in environment configuration | ||||
| - *(domain)* Dispatch refreshStatus event after successful domain update | ||||
| - *(database)* Correct container name generation for service databases | ||||
| - *(database)* Limit container name length for database proxy | ||||
| - *(database)* Handle unsupported database types in StartDatabaseProxy | ||||
| - *(database)* Simplify container name generation in StartDatabaseProxy | ||||
| - *(install)* Handle potential errors in Docker address pool configuration | ||||
| - *(backups)* Retention settings | ||||
| - *(redis)* Set default redis_username for new instances | ||||
| - *(core)* Improve instantSave logic and error handling | ||||
| - *(general)* Correct link to framework specific documentation | ||||
| - *(core)* Redirect healthcheck route for dockercompose applications | ||||
| - *(api)* Use name from request payload | ||||
| @@ -458,7 +558,6 @@ All notable changes to this project will be documented in this file. | ||||
| 
 | ||||
| ### ⚙️ Miscellaneous Tasks | ||||
| 
 | ||||
| - *(supabase)* Update Supabase service template and Postgres image version | ||||
| - *(migration)* Remove unused columns | ||||
| - *(ssl)* Improve code in ssl helper | ||||
| - *(migration)* Ssl cert and key should not be nullable | ||||
| @@ -466,6 +565,7 @@ All notable changes to this project will be documented in this file. | ||||
| - Rename ca crt folder to ssl | ||||
| - *(ui)* Improve valid until handling | ||||
| - Improve code quality suggested by code rabbit | ||||
| - *(supabase)* Update Supabase service template and Postgres image version | ||||
| - *(versions)* Update version numbers for coolify and nightly | ||||
| 
 | ||||
| ## [4.0.0-beta.398] - 2025-03-01 | ||||
| @@ -858,14 +958,6 @@ All notable changes to this project will be documented in this file. | ||||
| 
 | ||||
| ### 🚀 Features | ||||
| 
 | ||||
| - New ServerReachabilityChanged event | ||||
| - Use new ServerReachabilityChanged event instead of isDirty | ||||
| - Add infomaniak oauth | ||||
| - Add server disk usage check frequency | ||||
| - Add environment_uuid support and update API documentation | ||||
| - Add service/resource/project labels | ||||
| - Add coolify.environment label | ||||
| - Add database subtype | ||||
| - Able to import full db backups for pg/mysql/mariadb | ||||
| - Restore backup from server file | ||||
| - Docker volume data cloning | ||||
| @@ -901,35 +993,6 @@ All notable changes to this project will be documented in this file. | ||||
| 
 | ||||
| ### 🐛 Bug Fixes | ||||
| 
 | ||||
| - Fallback for copy button | ||||
| - Copy the right text | ||||
| - Maybe fallback is now working | ||||
| - Only show copy button on secure context | ||||
| - Render html on error page correctly | ||||
| - Invalid API response on missing project | ||||
| - Applications API response code + schema | ||||
| - Applications API writing to unavailable models | ||||
| - If an init script is renamed the old version is still on the server | ||||
| - Oauthseeder | ||||
| - Compose loading seq | ||||
| - Resource clone name + volume name generation | ||||
| - Update Dockerfile entrypoint path to /etc/entrypoint.d | ||||
| - Debug mode | ||||
| - Unreachable notifications | ||||
| - Remove duplicated ServerCheckJob call | ||||
| - Few fixes and use new ServerReachabilityChanged event | ||||
| - Use serverStatus not just status | ||||
| - Oauth seeder | ||||
| - Service ui structure | ||||
| - Check port 8080 and fallback to 80 | ||||
| - Refactor database view | ||||
| - Always use docker cleanup frequency | ||||
| - Advanced server UI | ||||
| - Html css | ||||
| - Fix domain being override when update application | ||||
| - Use nixpacks predefined build variables, but still could update the default values from Coolify | ||||
| - Use local monaco-editor instead of Cloudflare | ||||
| - N8n timezone | ||||
| - Compose envs | ||||
| - Scheduled tasks and backups are executed by server timezone. | ||||
| - Show backup timezone on the UI | ||||
| @@ -1029,7 +1092,6 @@ All notable changes to this project will be documented in this file. | ||||
| 
 | ||||
| ### 🚜 Refactor | ||||
| 
 | ||||
| - Rename `coolify.environment` to `coolify.environmentName` | ||||
| - Rename parameter in DatabaseBackupJob for clarity | ||||
| - Improve checkbox component accessibility and styling | ||||
| - Remove unused tags method from ApplicationDeploymentJob | ||||
| @@ -1045,9 +1107,6 @@ All notable changes to this project will be documented in this file. | ||||
| 
 | ||||
| ### ⚙️ Miscellaneous Tasks | ||||
| 
 | ||||
| - Regenerate API spec, removing notification fields | ||||
| - Remove ray debugging | ||||
| - Version ++ | ||||
| - Improve Penpot healthchecks | ||||
| - Switch up readonly lables to make more sense | ||||
| - Remove unused computed fields | ||||
| @@ -1071,11 +1130,44 @@ All notable changes to this project will be documented in this file. | ||||
| 
 | ||||
| ### 🚀 Features | ||||
| 
 | ||||
| - New ServerReachabilityChanged event | ||||
| - Use new ServerReachabilityChanged event instead of isDirty | ||||
| - Add infomaniak oauth | ||||
| - Add server disk usage check frequency | ||||
| - Add environment_uuid support and update API documentation | ||||
| - Add service/resource/project labels | ||||
| - Add coolify.environment label | ||||
| - Add database subtype | ||||
| - Migrate to new encryption options | ||||
| - New encryption options | ||||
| 
 | ||||
| ### 🐛 Bug Fixes | ||||
| 
 | ||||
| - Render html on error page correctly | ||||
| - Invalid API response on missing project | ||||
| - Applications API response code + schema | ||||
| - Applications API writing to unavailable models | ||||
| - If an init script is renamed the old version is still on the server | ||||
| - Oauthseeder | ||||
| - Compose loading seq | ||||
| - Resource clone name + volume name generation | ||||
| - Update Dockerfile entrypoint path to /etc/entrypoint.d | ||||
| - Debug mode | ||||
| - Unreachable notifications | ||||
| - Remove duplicated ServerCheckJob call | ||||
| - Few fixes and use new ServerReachabilityChanged event | ||||
| - Use serverStatus not just status | ||||
| - Oauth seeder | ||||
| - Service ui structure | ||||
| - Check port 8080 and fallback to 80 | ||||
| - Refactor database view | ||||
| - Always use docker cleanup frequency | ||||
| - Advanced server UI | ||||
| - Html css | ||||
| - Fix domain being override when update application | ||||
| - Use nixpacks predefined build variables, but still could update the default values from Coolify | ||||
| - Use local monaco-editor instead of Cloudflare | ||||
| - N8n timezone | ||||
| - Smtp encryption | ||||
| - Bind() to 0.0.0.0:80 failed | ||||
| - Oauth seeder | ||||
| @@ -1085,11 +1177,15 @@ All notable changes to this project will be documented in this file. | ||||
| - Error message | ||||
| - Update healthcheck and port configurations to use port 8080 | ||||
| 
 | ||||
| ## [4.0.0-beta.379] - 2024-12-13 | ||||
| ### 🚜 Refactor | ||||
| 
 | ||||
| ### 🐛 Bug Fixes | ||||
| - Rename `coolify.environment` to `coolify.environmentName` | ||||
| 
 | ||||
| - Saving oauth | ||||
| ### ⚙️ Miscellaneous Tasks | ||||
| 
 | ||||
| - Regenerate API spec, removing notification fields | ||||
| - Remove ray debugging | ||||
| - Version ++ | ||||
| 
 | ||||
| ## [4.0.0-beta.378] - 2024-12-13 | ||||
| 
 | ||||
| @@ -1098,6 +1194,11 @@ All notable changes to this project will be documented in this file. | ||||
| - Monaco editor light and dark mode switching | ||||
| - Service status indicator + oauth saving | ||||
| - Socialite for azure and authentik | ||||
| - Saving oauth | ||||
| - Fallback for copy button | ||||
| - Copy the right text | ||||
| - Maybe fallback is now working | ||||
| - Only show copy button on secure context | ||||
| 
 | ||||
| ## [4.0.0-beta.377] - 2024-12-13 | ||||
| 
 | ||||
|   | ||||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							| @@ -186,7 +186,7 @@ | ||||
|       same "printed page" as the copyright notice for easier | ||||
|       identification within third-party archives. | ||||
|  | ||||
|    Copyright [2022] [Andras Bacsai] | ||||
|    Copyright [2025] [Andras Bacsai] | ||||
|  | ||||
|    Licensed under the Apache License, Version 2.0 (the "License"); | ||||
|    you may not use this file except in compliance with the License. | ||||
|   | ||||
| @@ -84,6 +84,8 @@ Thank you so much! | ||||
| * [Tolgee](https://tolgee.io?ref=coolify.io) - The open source localization platform | ||||
| * [CompAI](https://www.trycomp.ai?ref=coolify.io) - Open source compliance automation platform | ||||
| * [GoldenVM](https://billing.goldenvm.com?ref=coolify.io) - Premium virtual machine hosting solutions | ||||
| * [Gozunga](https://gozunga.com?ref=coolify.io) - Seriously Simple Cloud Infrastructure | ||||
| * [Macarne](https://macarne.com?ref=coolify.io) - Best IP Transit & Carrier Ethernet Solutions for Simplified Network Connectivity | ||||
| 
 | ||||
| ## Small Sponsors | ||||
| 
 | ||||
|   | ||||
| @@ -30,7 +30,7 @@ class StopApplication | ||||
|             $application->stopContainers($containersToStop, $server); | ||||
| 
 | ||||
|             if ($application->build_pack === 'dockercompose') { | ||||
|                 $application->delete_connected_networks($application->uuid); | ||||
|                 $application->deleteConnectedNetworks(); | ||||
|             } | ||||
| 
 | ||||
|             if ($dockerCleanup) { | ||||
|   | ||||
| @@ -85,7 +85,6 @@ class RunRemoteProcess | ||||
|         ]); | ||||
| 
 | ||||
|         $processResult = $process->wait(); | ||||
|         // $processResult = Process::timeout($timeout)->run($this->getCommand(), $this->handleOutput(...));
 | ||||
|         if ($this->activity->properties->get('status') === ProcessStatus::ERROR->value) { | ||||
|             $status = ProcessStatus::ERROR; | ||||
|         } else { | ||||
|   | ||||
| @@ -11,7 +11,6 @@ use App\Models\StandaloneMongodb; | ||||
| use App\Models\StandaloneMysql; | ||||
| use App\Models\StandalonePostgresql; | ||||
| use App\Models\StandaloneRedis; | ||||
| use Illuminate\Support\Facades\Process; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| 
 | ||||
| class StopDatabase | ||||
| @@ -25,7 +24,7 @@ class StopDatabase | ||||
|             return 'Server is not functional'; | ||||
|         } | ||||
| 
 | ||||
|         $this->stopContainer($database, $database->uuid, 300); | ||||
|         $this->stopContainer($database, $database->uuid, 30); | ||||
|         if ($isDeleteOperation) { | ||||
|             if ($dockerCleanup) { | ||||
|                 CleanupDocker::dispatch($server, true); | ||||
| @@ -39,37 +38,12 @@ class StopDatabase | ||||
|         return 'Database stopped successfully'; | ||||
|     } | ||||
| 
 | ||||
|     private function stopContainer($database, string $containerName, int $timeout = 300): void | ||||
|     private function stopContainer($database, string $containerName, int $timeout = 30): void | ||||
|     { | ||||
|         $server = $database->destination->server; | ||||
| 
 | ||||
|         $process = Process::timeout($timeout)->start("docker stop --time=$timeout $containerName"); | ||||
| 
 | ||||
|         $startTime = time(); | ||||
|         while ($process->running()) { | ||||
|             if (time() - $startTime >= $timeout) { | ||||
|                 $this->forceStopContainer($containerName, $server); | ||||
|                 break; | ||||
|             } | ||||
|             usleep(100000); | ||||
|         } | ||||
| 
 | ||||
|         $this->removeContainer($containerName, $server); | ||||
|     } | ||||
| 
 | ||||
|     private function forceStopContainer(string $containerName, $server): void | ||||
|     { | ||||
|         instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false); | ||||
|     } | ||||
| 
 | ||||
|     private function removeContainer(string $containerName, $server): void | ||||
|     { | ||||
|         instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false); | ||||
|     } | ||||
| 
 | ||||
|     private function deleteConnectedNetworks($uuid, $server) | ||||
|     { | ||||
|         instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false); | ||||
|         instant_remote_process(["docker network rm {$uuid}"], $server, false); | ||||
|         instant_remote_process(command: [ | ||||
|             "docker stop --time=$timeout $containerName", | ||||
|             "docker rm -f $containerName", | ||||
|         ], server: $server, throwError: false); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,54 +3,27 @@ | ||||
| namespace App\Actions\Proxy; | ||||
| 
 | ||||
| use App\Models\Server; | ||||
| use Carbon\Carbon; | ||||
| use Illuminate\Process\InvokedProcess; | ||||
| use Illuminate\Support\Facades\Process; | ||||
| use Lorisleiva\Actions\Concerns\AsAction; | ||||
| 
 | ||||
| class StopProxy | ||||
| { | ||||
|     use AsAction; | ||||
| 
 | ||||
|     public function handle(Server $server, bool $forceStop = true) | ||||
|     public function handle(Server $server, bool $forceStop = true, int $timeout = 30) | ||||
|     { | ||||
|         try { | ||||
|             $containerName = $server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy'; | ||||
|             $timeout = 30; | ||||
| 
 | ||||
|             $process = $this->stopContainer($containerName, $timeout); | ||||
|             instant_remote_process(command: [ | ||||
|                 "docker stop --time=$timeout $containerName", | ||||
|                 "docker rm -f $containerName", | ||||
|             ], server: $server, throwError: false); | ||||
| 
 | ||||
|             $startTime = Carbon::now()->getTimestamp(); | ||||
|             while ($process->running()) { | ||||
|                 if (Carbon::now()->getTimestamp() - $startTime >= $timeout) { | ||||
|                     $this->forceStopContainer($containerName, $server); | ||||
|                     break; | ||||
|                 } | ||||
|                 usleep(100000); | ||||
|             } | ||||
| 
 | ||||
|             $this->removeContainer($containerName, $server); | ||||
|         } catch (\Throwable $e) { | ||||
|             return handleError($e); | ||||
|         } finally { | ||||
|             $server->proxy->force_stop = $forceStop; | ||||
|             $server->proxy->status = 'exited'; | ||||
|             $server->save(); | ||||
|         } catch (\Throwable $e) { | ||||
|             return handleError($e); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function stopContainer(string $containerName, int $timeout): InvokedProcess | ||||
|     { | ||||
|         return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName"); | ||||
|     } | ||||
| 
 | ||||
|     private function forceStopContainer(string $containerName, Server $server) | ||||
|     { | ||||
|         instant_remote_process(["docker kill $containerName"], $server, throwError: false); | ||||
|     } | ||||
| 
 | ||||
|     private function removeContainer(string $containerName, Server $server) | ||||
|     { | ||||
|         instant_remote_process(["docker rm -f $containerName"], $server, throwError: false); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -99,7 +99,8 @@ class ServerCheck | ||||
|                             return data_get($value, 'Name') === '/coolify-proxy'; | ||||
|                         } | ||||
|                     })->first(); | ||||
|                     if (! $foundProxyContainer) { | ||||
|                     $proxyStatus = data_get($foundProxyContainer, 'State.Status', 'exited'); | ||||
|                     if (! $foundProxyContainer || $proxyStatus !== 'running') { | ||||
|                         try { | ||||
|                             $shouldStart = CheckProxy::run($this->server); | ||||
|                             if ($shouldStart) { | ||||
|   | ||||
| @@ -15,19 +15,18 @@ class StartLogDrain | ||||
|     { | ||||
|         if ($server->settings->is_logdrain_newrelic_enabled) { | ||||
|             $type = 'newrelic'; | ||||
|             StopLogDrain::run($server); | ||||
|         } elseif ($server->settings->is_logdrain_highlight_enabled) { | ||||
|             $type = 'highlight'; | ||||
|             StopLogDrain::run($server); | ||||
|         } elseif ($server->settings->is_logdrain_axiom_enabled) { | ||||
|             $type = 'axiom'; | ||||
|             StopLogDrain::run($server); | ||||
|         } elseif ($server->settings->is_logdrain_custom_enabled) { | ||||
|             $type = 'custom'; | ||||
|             StopLogDrain::run($server); | ||||
|         } else { | ||||
|             $type = 'none'; | ||||
|         } | ||||
|         if ($type !== 'none') { | ||||
|             StopLogDrain::run($server); | ||||
|         } | ||||
|         try { | ||||
|             if ($type === 'none') { | ||||
|                 return 'No log drain is enabled.'; | ||||
| @@ -186,7 +185,6 @@ Files: | ||||
|                 "echo '{$compose}' | base64 -d | tee $compose_path > /dev/null", | ||||
|                 "echo '{$readme}' | base64 -d | tee $readme_path > /dev/null", | ||||
|                 "test -f $config_path/.env && rm $config_path/.env", | ||||
| 
 | ||||
|             ]; | ||||
|             if ($type === 'newrelic') { | ||||
|                 $add_envs_command = [ | ||||
|   | ||||
| @@ -48,7 +48,7 @@ class DeleteService | ||||
|             } | ||||
| 
 | ||||
|             if ($deleteConnectedNetworks) { | ||||
|                 $service->delete_connected_networks($service->uuid); | ||||
|                 $service->deleteConnectedNetworks(); | ||||
|             } | ||||
| 
 | ||||
|             instant_remote_process(["docker rm -f $service->uuid"], $server, throwError: false); | ||||
| @@ -56,7 +56,7 @@ class DeleteService | ||||
|             throw new \Exception($e->getMessage()); | ||||
|         } finally { | ||||
|             if ($deleteConfigurations) { | ||||
|                 $service->delete_configurations(); | ||||
|                 $service->deleteConfigurations(); | ||||
|             } | ||||
|             foreach ($service->applications()->get() as $application) { | ||||
|                 $application->forceDelete(); | ||||
|   | ||||
| @@ -24,7 +24,7 @@ class StopService | ||||
|             $service->stopContainers($containersToStop, $server); | ||||
| 
 | ||||
|             if ($isDeleteOperation) { | ||||
|                 $service->delete_connected_networks($service->uuid); | ||||
|                 $service->deleteConnectedNetworks(); | ||||
|                 if ($dockerCleanup) { | ||||
|                     CleanupDocker::dispatch($server, true); | ||||
|                 } | ||||
|   | ||||
| @@ -13,17 +13,20 @@ class CleanupRedis extends Command | ||||
| 
 | ||||
|     public function handle() | ||||
|     { | ||||
|         $prefix = config('database.redis.options.prefix'); | ||||
| 
 | ||||
|         $keys = Redis::connection()->keys('*:laravel*'); | ||||
|         collect($keys)->each(function ($key) use ($prefix) { | ||||
|         $redis = Redis::connection('horizon'); | ||||
|         $keys = $redis->keys('*'); | ||||
|         $prefix = config('horizon.prefix'); | ||||
|         foreach ($keys as $key) { | ||||
|             $keyWithoutPrefix = str_replace($prefix, '', $key); | ||||
|             Redis::connection()->del($keyWithoutPrefix); | ||||
|         }); | ||||
|             $type = $redis->command('type', [$keyWithoutPrefix]); | ||||
| 
 | ||||
|         $queueOverlaps = Redis::connection()->keys('*laravel-queue-overlap*'); | ||||
|         collect($queueOverlaps)->each(function ($key) { | ||||
|             Redis::connection()->del($key); | ||||
|         }); | ||||
|             if ($type === 5) { | ||||
|                 $data = $redis->command('hgetall', [$keyWithoutPrefix]); | ||||
|                 $status = data_get($data, 'status'); | ||||
|                 if ($status === 'completed') { | ||||
|                     $redis->command('del', [$keyWithoutPrefix]); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -51,6 +51,7 @@ class Kernel extends ConsoleKernel | ||||
|         } | ||||
| 
 | ||||
|         // $this->scheduleInstance->job(new CleanupStaleMultiplexedConnections)->hourly();
 | ||||
|         $this->scheduleInstance->command('cleanup:redis')->hourly(); | ||||
| 
 | ||||
|         if (isDev()) { | ||||
|             // Instance Jobs
 | ||||
|   | ||||
| @@ -12,21 +12,22 @@ class ApplicationStatusChanged implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public $teamId; | ||||
|     public ?int $teamId = null; | ||||
| 
 | ||||
|     public function __construct($teamId = null) | ||||
|     { | ||||
|         if (is_null($teamId)) { | ||||
|             $teamId = auth()->user()->currentTeam()->id ?? null; | ||||
|         } | ||||
|         if (is_null($teamId)) { | ||||
|             throw new \Exception('Team id is null'); | ||||
|         if (is_null($teamId) && auth()->check() && auth()->user()->currentTeam()) { | ||||
|             $teamId = auth()->user()->currentTeam()->id; | ||||
|         } | ||||
|         $this->teamId = $teamId; | ||||
|     } | ||||
| 
 | ||||
|     public function broadcastOn(): array | ||||
|     { | ||||
|         if (is_null($this->teamId)) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             new PrivateChannel("team.{$this->teamId}"), | ||||
|         ]; | ||||
|   | ||||
| @@ -12,21 +12,22 @@ class BackupCreated implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public $teamId; | ||||
|     public ?int $teamId = null; | ||||
| 
 | ||||
|     public function __construct($teamId = null) | ||||
|     { | ||||
|         if (is_null($teamId)) { | ||||
|             $teamId = auth()->user()->currentTeam()->id ?? null; | ||||
|         } | ||||
|         if (is_null($teamId)) { | ||||
|             throw new \Exception('Team id is null'); | ||||
|         if (is_null($teamId) && auth()->check() && auth()->user()->currentTeam()) { | ||||
|             $teamId = auth()->user()->currentTeam()->id; | ||||
|         } | ||||
|         $this->teamId = $teamId; | ||||
|     } | ||||
| 
 | ||||
|     public function broadcastOn(): array | ||||
|     { | ||||
|         if (is_null($this->teamId)) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             new PrivateChannel("team.{$this->teamId}"), | ||||
|         ]; | ||||
|   | ||||
| @@ -12,21 +12,22 @@ class CloudflareTunnelConfigured implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public $teamId; | ||||
|     public ?int $teamId = null; | ||||
| 
 | ||||
|     public function __construct($teamId = null) | ||||
|     { | ||||
|         if (is_null($teamId)) { | ||||
|             $teamId = auth()->user()->currentTeam()->id ?? null; | ||||
|         } | ||||
|         if (is_null($teamId)) { | ||||
|             throw new \Exception('Team id is null'); | ||||
|         if (is_null($teamId) && auth()->check() && auth()->user()->currentTeam()) { | ||||
|             $teamId = auth()->user()->currentTeam()->id; | ||||
|         } | ||||
|         $this->teamId = $teamId; | ||||
|     } | ||||
| 
 | ||||
|     public function broadcastOn(): array | ||||
|     { | ||||
|         if (is_null($this->teamId)) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             new PrivateChannel("team.{$this->teamId}"), | ||||
|         ]; | ||||
|   | ||||
| @@ -7,27 +7,27 @@ use Illuminate\Broadcasting\PrivateChannel; | ||||
| use Illuminate\Contracts\Broadcasting\ShouldBroadcast; | ||||
| use Illuminate\Foundation\Events\Dispatchable; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| use Illuminate\Support\Facades\Auth; | ||||
| 
 | ||||
| class DatabaseProxyStopped implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public $teamId; | ||||
|     public ?int $teamId = null; | ||||
| 
 | ||||
|     public function __construct($teamId = null) | ||||
|     { | ||||
|         if (is_null($teamId)) { | ||||
|             $teamId = Auth::user()?->currentTeam()?->id ?? null; | ||||
|         } | ||||
|         if (is_null($teamId)) { | ||||
|             throw new \Exception('Team id is null'); | ||||
|         if (is_null($teamId) && auth()->check() && auth()->user()->currentTeam()) { | ||||
|             $teamId = auth()->user()->currentTeam()->id; | ||||
|         } | ||||
|         $this->teamId = $teamId; | ||||
|     } | ||||
| 
 | ||||
|     public function broadcastOn(): array | ||||
|     { | ||||
|         if (is_null($this->teamId)) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             new PrivateChannel("team.{$this->teamId}"), | ||||
|         ]; | ||||
|   | ||||
| @@ -13,28 +13,24 @@ class DatabaseStatusChanged implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public $userId = null; | ||||
|     public int|string|null $userId = null; | ||||
| 
 | ||||
|     public function __construct($userId = null) | ||||
|     { | ||||
|         if (is_null($userId)) { | ||||
|             $userId = Auth::id() ?? null; | ||||
|         } | ||||
|         if (is_null($userId)) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         $this->userId = $userId; | ||||
|     } | ||||
| 
 | ||||
|     public function broadcastOn(): ?array | ||||
|     { | ||||
|         if (! is_null($this->userId)) { | ||||
|             return [ | ||||
|                 new PrivateChannel("user.{$this->userId}"), | ||||
|             ]; | ||||
|         if (is_null($this->userId)) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         return null; | ||||
|         return [ | ||||
|             new PrivateChannel("user.{$this->userId}"), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -12,18 +12,22 @@ class FileStorageChanged implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public $teamId; | ||||
|     public ?int $teamId = null; | ||||
| 
 | ||||
|     public function __construct($teamId = null) | ||||
|     { | ||||
|         if (is_null($teamId)) { | ||||
|             throw new \Exception('Team id is null'); | ||||
|         if (is_null($teamId) && auth()->check() && auth()->user()->currentTeam()) { | ||||
|             $teamId = auth()->user()->currentTeam()->id; | ||||
|         } | ||||
|         $this->teamId = $teamId; | ||||
|     } | ||||
| 
 | ||||
|     public function broadcastOn(): array | ||||
|     { | ||||
|         if (is_null($this->teamId)) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             new PrivateChannel("team.{$this->teamId}"), | ||||
|         ]; | ||||
|   | ||||
| @@ -12,21 +12,22 @@ class ProxyStatusChanged implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public $teamId; | ||||
|     public ?int $teamId = null; | ||||
| 
 | ||||
|     public function __construct($teamId = null) | ||||
|     { | ||||
|         if (is_null($teamId)) { | ||||
|             $teamId = auth()->user()->currentTeam()->id ?? null; | ||||
|         } | ||||
|         if (is_null($teamId)) { | ||||
|             throw new \Exception('Team id is null'); | ||||
|         if (is_null($teamId) && auth()->check() && auth()->user()->currentTeam()) { | ||||
|             $teamId = auth()->user()->currentTeam()->id; | ||||
|         } | ||||
|         $this->teamId = $teamId; | ||||
|     } | ||||
| 
 | ||||
|     public function broadcastOn(): array | ||||
|     { | ||||
|         if (is_null($this->teamId)) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             new PrivateChannel("team.{$this->teamId}"), | ||||
|         ]; | ||||
|   | ||||
| @@ -12,21 +12,22 @@ class ScheduledTaskDone implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public $teamId; | ||||
|     public ?int $teamId = null; | ||||
| 
 | ||||
|     public function __construct($teamId = null) | ||||
|     { | ||||
|         if (is_null($teamId)) { | ||||
|             $teamId = auth()->user()->currentTeam()->id ?? null; | ||||
|         } | ||||
|         if (is_null($teamId)) { | ||||
|             throw new \Exception('Team id is null'); | ||||
|         if (is_null($teamId) && auth()->check() && auth()->user()->currentTeam()) { | ||||
|             $teamId = auth()->user()->currentTeam()->id; | ||||
|         } | ||||
|         $this->teamId = $teamId; | ||||
|     } | ||||
| 
 | ||||
|     public function broadcastOn(): array | ||||
|     { | ||||
|         if (is_null($this->teamId)) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             new PrivateChannel("team.{$this->teamId}"), | ||||
|         ]; | ||||
|   | ||||
| @@ -13,27 +13,24 @@ class ServiceStatusChanged implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public ?string $userId = null; | ||||
|     public int|string|null $userId = null; | ||||
| 
 | ||||
|     public function __construct($userId = null) | ||||
|     { | ||||
|         if (is_null($userId)) { | ||||
|             $userId = Auth::id() ?? null; | ||||
|         } | ||||
|         if (is_null($userId)) { | ||||
|             return false; | ||||
|         } | ||||
|         $this->userId = $userId; | ||||
|     } | ||||
| 
 | ||||
|     public function broadcastOn(): ?array | ||||
|     { | ||||
|         if (! is_null($this->userId)) { | ||||
|             return [ | ||||
|                 new PrivateChannel("user.{$this->userId}"), | ||||
|             ]; | ||||
|         if (is_null($this->userId)) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         return null; | ||||
|         return [ | ||||
|             new PrivateChannel("user.{$this->userId}"), | ||||
|         ]; | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -12,15 +12,21 @@ class TestEvent implements ShouldBroadcast | ||||
| { | ||||
|     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||
| 
 | ||||
|     public $teamId; | ||||
|     public ?int $teamId = null; | ||||
| 
 | ||||
|     public function __construct() | ||||
|     { | ||||
|         $this->teamId = auth()->user()->currentTeam()->id; | ||||
|         if (auth()->check() && auth()->user()->currentTeam()) { | ||||
|             $this->teamId = auth()->user()->currentTeam()->id; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function broadcastOn(): array | ||||
|     { | ||||
|         if (is_null($this->teamId)) { | ||||
|             return []; | ||||
|         } | ||||
| 
 | ||||
|         return [ | ||||
|             new PrivateChannel("team.{$this->teamId}"), | ||||
|         ]; | ||||
|   | ||||
| @@ -27,7 +27,6 @@ use Illuminate\Foundation\Bus\Dispatchable; | ||||
| use Illuminate\Queue\InteractsWithQueue; | ||||
| use Illuminate\Queue\SerializesModels; | ||||
| use Illuminate\Support\Collection; | ||||
| use Illuminate\Support\Facades\Process; | ||||
| use Illuminate\Support\Sleep; | ||||
| use Illuminate\Support\Str; | ||||
| use RuntimeException; | ||||
| @@ -1377,7 +1376,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue | ||||
| 
 | ||||
|     private function check_git_if_build_needed() | ||||
|     { | ||||
|         if ($this->source->getMorphClass() === \App\Models\GithubApp::class && $this->source->is_public === false) { | ||||
|         if (is_object($this->source) && $this->source->getMorphClass() === \App\Models\GithubApp::class && $this->source->is_public === false) { | ||||
|             $repository = githubApi($this->source, "repos/{$this->customRepository}"); | ||||
|             $data = data_get($repository, 'data'); | ||||
|             if (isset($data->id)) { | ||||
| @@ -2246,43 +2245,16 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); | ||||
|         $this->application_deployment_queue->addLogEntry('Building docker image completed.'); | ||||
|     } | ||||
| 
 | ||||
|     private function graceful_shutdown_container(string $containerName, int $timeout = 300) | ||||
|     private function graceful_shutdown_container(string $containerName, int $timeout = 30) | ||||
|     { | ||||
|         try { | ||||
|             $process = Process::timeout($timeout)->start("docker stop --time=$timeout $containerName"); | ||||
| 
 | ||||
|             $startTime = time(); | ||||
|             while ($process->running()) { | ||||
|                 if (time() - $startTime >= $timeout) { | ||||
|                     $this->execute_remote_command( | ||||
|                         ["docker kill $containerName", 'hidden' => true, 'ignore_errors' => true] | ||||
|                     ); | ||||
|                     break; | ||||
|                 } | ||||
|                 usleep(100000); | ||||
|             } | ||||
| 
 | ||||
|             $isRunning = $this->execute_remote_command( | ||||
|                 ["docker inspect -f '{{.State.Running}}' $containerName", 'hidden' => true, 'ignore_errors' => true] | ||||
|             ) === 'true'; | ||||
| 
 | ||||
|             if ($isRunning) { | ||||
|                 $this->execute_remote_command( | ||||
|                     ["docker kill $containerName", 'hidden' => true, 'ignore_errors' => true] | ||||
|                 ); | ||||
|             } | ||||
|         } catch (\Exception $error) { | ||||
|             $this->execute_remote_command( | ||||
|                 ["docker stop --time=$timeout $containerName", 'hidden' => true, 'ignore_errors' => true], | ||||
|                 ["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true] | ||||
|             ); | ||||
|         } catch (Exception $error) { | ||||
|             $this->application_deployment_queue->addLogEntry("Error stopping container $containerName: ".$error->getMessage(), 'stderr'); | ||||
|         } | ||||
| 
 | ||||
|         $this->remove_container($containerName); | ||||
|     } | ||||
| 
 | ||||
|     private function remove_container(string $containerName) | ||||
|     { | ||||
|         $this->execute_remote_command( | ||||
|             ["docker rm -f $containerName", 'hidden' => true, 'ignore_errors' => true] | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     private function stop_running_container(bool $force = false) | ||||
|   | ||||
| @@ -23,7 +23,7 @@ class CleanupInstanceStuffsJob implements ShouldBeEncrypted, ShouldBeUnique, Sho | ||||
| 
 | ||||
|     public function middleware(): array | ||||
|     { | ||||
|         return [(new WithoutOverlapping('cleanup-instance-stuffs'))->expireAfter(60)]; | ||||
|         return [(new WithoutOverlapping('cleanup-instance-stuffs'))->dontRelease()]; | ||||
|     } | ||||
| 
 | ||||
|     public function handle(): void | ||||
|   | ||||
| @@ -390,7 +390,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue | ||||
|             $commands[] = 'mkdir -p '.$this->backup_dir; | ||||
|             $backupCommand = 'docker exec'; | ||||
|             if ($this->postgres_password) { | ||||
|                 $backupCommand .= " -e PGPASSWORD=$this->postgres_password"; | ||||
|                 $backupCommand .= " -e PGPASSWORD=\"{$this->postgres_password}\""; | ||||
|             } | ||||
|             if ($this->backup->dump_all) { | ||||
|                 $backupCommand .= " $this->container_name pg_dumpall --username {$this->database->postgres_user} | gzip > $this->backup_location"; | ||||
|   | ||||
| @@ -42,10 +42,8 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue | ||||
|     public function handle() | ||||
|     { | ||||
|         try { | ||||
|             $persistentStorages = collect(); | ||||
|             switch ($this->resource->type()) { | ||||
|                 case 'application': | ||||
|                     $persistentStorages = $this->resource?->persistentStorages()?->get(); | ||||
|                     StopApplication::run($this->resource, previewDeployments: true); | ||||
|                     break; | ||||
|                 case 'standalone-postgresql': | ||||
| @@ -56,53 +54,52 @@ class DeleteResourceJob implements ShouldBeEncrypted, ShouldQueue | ||||
|                 case 'standalone-keydb': | ||||
|                 case 'standalone-dragonfly': | ||||
|                 case 'standalone-clickhouse': | ||||
|                     $persistentStorages = $this->resource?->persistentStorages()?->get(); | ||||
|                     StopDatabase::run($this->resource, true); | ||||
|                     break; | ||||
|                 case 'service': | ||||
|                     StopService::run($this->resource, true); | ||||
|                     DeleteService::run($this->resource, $this->deleteConfigurations, $this->deleteVolumes, $this->dockerCleanup, $this->deleteConnectedNetworks); | ||||
|                     break; | ||||
|             } | ||||
| 
 | ||||
|             if ($this->deleteVolumes && $this->resource->type() !== 'service') { | ||||
|                 $this->resource->delete_volumes($persistentStorages); | ||||
|                 $this->resource->persistentStorages()->delete(); | ||||
|                     return; | ||||
|             } | ||||
|             $isDatabase = $this->resource instanceof StandalonePostgresql | ||||
|                 || $this->resource instanceof StandaloneRedis | ||||
|                 || $this->resource instanceof StandaloneMongodb | ||||
|                 || $this->resource instanceof StandaloneMysql | ||||
|                 || $this->resource instanceof StandaloneMariadb | ||||
|                 || $this->resource instanceof StandaloneKeydb | ||||
|                 || $this->resource instanceof StandaloneDragonfly | ||||
|                 || $this->resource instanceof StandaloneClickhouse; | ||||
| 
 | ||||
|             if ($this->deleteConfigurations) { | ||||
|                 $this->resource->delete_configurations(); // rename to FileStorages
 | ||||
|                 $this->resource->fileStorages()->delete(); | ||||
|                 $this->resource->deleteConfigurations(); | ||||
|             } | ||||
|             if ($this->deleteVolumes) { | ||||
|                 $this->resource->deleteVolumes(); | ||||
|                 $this->resource->persistentStorages()->delete(); | ||||
|             } | ||||
|             $this->resource->fileStorages()->delete(); | ||||
| 
 | ||||
|             $isDatabase = $this->resource instanceof StandalonePostgresql | ||||
|             || $this->resource instanceof StandaloneRedis | ||||
|             || $this->resource instanceof StandaloneMongodb | ||||
|             || $this->resource instanceof StandaloneMysql | ||||
|             || $this->resource instanceof StandaloneMariadb | ||||
|             || $this->resource instanceof StandaloneKeydb | ||||
|             || $this->resource instanceof StandaloneDragonfly | ||||
|             || $this->resource instanceof StandaloneClickhouse; | ||||
| 
 | ||||
|             if ($isDatabase) { | ||||
|                 $this->resource->sslCertificates()->delete(); | ||||
|                 $this->resource->scheduledBackups()->delete(); | ||||
|                 $this->resource->environment_variables()->delete(); | ||||
|                 $this->resource->tags()->detach(); | ||||
|             } | ||||
|             $this->resource->environment_variables()->delete(); | ||||
| 
 | ||||
|             $server = data_get($this->resource, 'server') ?? data_get($this->resource, 'destination.server'); | ||||
|             if (($this->dockerCleanup || $isDatabase) && $server) { | ||||
|                 CleanupDocker::dispatch($server, true); | ||||
|             } | ||||
| 
 | ||||
|             if ($this->deleteConnectedNetworks && ! $isDatabase) { | ||||
|                 $this->resource?->delete_connected_networks($this->resource->uuid); | ||||
|             if ($this->deleteConnectedNetworks && $this->resource->type() === 'application') { | ||||
|                 $this->resource->deleteConnectedNetworks(); | ||||
|             } | ||||
|         } catch (\Throwable $e) { | ||||
|             throw $e; | ||||
|         } finally { | ||||
|             $this->resource->forceDelete(); | ||||
|             if ($this->dockerCleanup) { | ||||
|                 CleanupDocker::dispatch($server, true); | ||||
|                 $server = data_get($this->resource, 'server') ?? data_get($this->resource, 'destination.server'); | ||||
|                 if ($server) { | ||||
|                     CleanupDocker::dispatch($server, true); | ||||
|                 } | ||||
|             } | ||||
|             Artisan::queue('cleanup:stucked-resources'); | ||||
|         } | ||||
|   | ||||
| @@ -31,7 +31,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue | ||||
| 
 | ||||
|     public function middleware(): array | ||||
|     { | ||||
|         return [(new WithoutOverlapping($this->server->uuid))->expireAfter(600)]; | ||||
|         return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; | ||||
|     } | ||||
| 
 | ||||
|     public function __construct(public Server $server, public bool $manualCleanup = false) {} | ||||
|   | ||||
| @@ -71,7 +71,7 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue | ||||
| 
 | ||||
|     public function middleware(): array | ||||
|     { | ||||
|         return [(new WithoutOverlapping($this->server->uuid))->expireAfter(30)]; | ||||
|         return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; | ||||
|     } | ||||
| 
 | ||||
|     public function backoff(): int | ||||
|   | ||||
| @@ -24,7 +24,7 @@ class RestartProxyJob implements ShouldBeEncrypted, ShouldQueue | ||||
| 
 | ||||
|     public function middleware(): array | ||||
|     { | ||||
|         return [(new WithoutOverlapping($this->server->uuid))->expireAfter(60)]; | ||||
|         return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; | ||||
|     } | ||||
| 
 | ||||
|     public function __construct(public Server $server) {} | ||||
|   | ||||
| @@ -28,7 +28,7 @@ class ServerCheckJob implements ShouldBeEncrypted, ShouldQueue | ||||
| 
 | ||||
|     public function middleware(): array | ||||
|     { | ||||
|         return [(new WithoutOverlapping($this->server->uuid))->expireAfter(60)]; | ||||
|         return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; | ||||
|     } | ||||
| 
 | ||||
|     public function __construct(public Server $server) {} | ||||
|   | ||||
| @@ -51,7 +51,7 @@ class Dashboard extends Component | ||||
| 
 | ||||
|     public function navigateToProject($projectUuid) | ||||
|     { | ||||
|         return $this->redirect(collect($this->projects)->firstWhere('uuid', $projectUuid)->navigateTo(), true); | ||||
|         return $this->redirect(collect($this->projects)->firstWhere('uuid', $projectUuid)->navigateTo(), navigate: false); | ||||
|     } | ||||
| 
 | ||||
|     public function render() | ||||
|   | ||||
| @@ -100,7 +100,7 @@ class Heading extends Component | ||||
|             'application_uuid' => $this->parameters['application_uuid'], | ||||
|             'deployment_uuid' => $this->deploymentUuid, | ||||
|             'environment_uuid' => $this->parameters['environment_uuid'], | ||||
|         ], navigate: true); | ||||
|         ], navigate: false); | ||||
|     } | ||||
| 
 | ||||
|     protected function setDeploymentUuid() | ||||
| @@ -147,7 +147,7 @@ class Heading extends Component | ||||
|             'application_uuid' => $this->parameters['application_uuid'], | ||||
|             'deployment_uuid' => $this->deploymentUuid, | ||||
|             'environment_uuid' => $this->parameters['environment_uuid'], | ||||
|         ], navigate: true); | ||||
|         ], navigate: false); | ||||
|     } | ||||
| 
 | ||||
|     public function render() | ||||
|   | ||||
| @@ -5,10 +5,7 @@ namespace App\Livewire\Project\Application; | ||||
| use App\Actions\Docker\GetContainersStatus; | ||||
| use App\Models\Application; | ||||
| use App\Models\ApplicationPreview; | ||||
| use Carbon\Carbon; | ||||
| use Illuminate\Process\InvokedProcess; | ||||
| use Illuminate\Support\Collection; | ||||
| use Illuminate\Support\Facades\Process; | ||||
| use Livewire\Component; | ||||
| use Spatie\Url\Url; | ||||
| use Visus\Cuid2\Cuid2; | ||||
| @@ -193,13 +190,12 @@ class Previews extends Component | ||||
|     { | ||||
|         try { | ||||
|             $server = $this->application->destination->server; | ||||
|             $timeout = 300; | ||||
| 
 | ||||
|             if ($this->application->destination->server->isSwarm()) { | ||||
|                 instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $server); | ||||
|             } else { | ||||
|                 $containers = getCurrentApplicationContainerStatus($server, $this->application->id, $pull_request_id)->toArray(); | ||||
|                 $this->stopContainers($containers, $server, $timeout); | ||||
|                 $this->stopContainers($containers, $server); | ||||
|             } | ||||
| 
 | ||||
|             GetContainersStatus::run($server); | ||||
| @@ -215,13 +211,12 @@ class Previews extends Component | ||||
|     { | ||||
|         try { | ||||
|             $server = $this->application->destination->server; | ||||
|             $timeout = 300; | ||||
| 
 | ||||
|             if ($this->application->destination->server->isSwarm()) { | ||||
|                 instant_remote_process(["docker stack rm {$this->application->uuid}-{$pull_request_id}"], $server); | ||||
|             } else { | ||||
|                 $containers = getCurrentApplicationContainerStatus($server, $this->application->id, $pull_request_id)->toArray(); | ||||
|                 $this->stopContainers($containers, $server, $timeout); | ||||
|                 $this->stopContainers($containers, $server); | ||||
|             } | ||||
| 
 | ||||
|             ApplicationPreview::where('application_id', $this->application->id) | ||||
| @@ -237,48 +232,14 @@ class Previews extends Component | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function stopContainers(array $containers, $server, int $timeout) | ||||
|     private function stopContainers(array $containers, $server, int $timeout = 30) | ||||
|     { | ||||
|         $processes = []; | ||||
|         foreach ($containers as $container) { | ||||
|             $containerName = str_replace('/', '', $container['Names']); | ||||
|             $processes[$containerName] = $this->stopContainer($containerName, $timeout); | ||||
|         } | ||||
| 
 | ||||
|         $startTime = Carbon::now()->getTimestamp(); | ||||
|         while (count($processes) > 0) { | ||||
|             $finishedProcesses = array_filter($processes, function ($process) { | ||||
|                 return ! $process->running(); | ||||
|             }); | ||||
|             foreach (array_keys($finishedProcesses) as $containerName) { | ||||
|                 unset($processes[$containerName]); | ||||
|                 $this->removeContainer($containerName, $server); | ||||
|             } | ||||
| 
 | ||||
|             if (Carbon::now()->getTimestamp() - $startTime >= $timeout) { | ||||
|                 $this->forceStopRemainingContainers(array_keys($processes), $server); | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             usleep(100000); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private function stopContainer(string $containerName, int $timeout): InvokedProcess | ||||
|     { | ||||
|         return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName"); | ||||
|     } | ||||
| 
 | ||||
|     private function removeContainer(string $containerName, $server) | ||||
|     { | ||||
|         instant_remote_process(["docker rm -f $containerName"], $server, throwError: false); | ||||
|     } | ||||
| 
 | ||||
|     private function forceStopRemainingContainers(array $containerNames, $server) | ||||
|     { | ||||
|         foreach ($containerNames as $containerName) { | ||||
|             instant_remote_process(["docker kill $containerName"], $server, throwError: false); | ||||
|             $this->removeContainer($containerName, $server); | ||||
|             instant_remote_process(command: [ | ||||
|                 "docker stop --time=$timeout $containerName", | ||||
|                 "docker rm -f $containerName", | ||||
|             ], server: $server, throwError: false); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -120,6 +120,8 @@ class General extends Component | ||||
|         try { | ||||
|             $this->database->save(); | ||||
|             $this->dispatch('success', 'SSL configuration updated.'); | ||||
|             $this->db_url = $this->database->internal_db_url; | ||||
|             $this->db_url_public = $this->database->external_db_url; | ||||
|         } catch (Exception $e) { | ||||
|             return handleError($e, $this); | ||||
|         } | ||||
|   | ||||
| @@ -19,6 +19,8 @@ class ScheduledBackups extends Component | ||||
| 
 | ||||
|     public $s3s; | ||||
| 
 | ||||
|     public string $custom_type = 'mysql'; | ||||
| 
 | ||||
|     protected $listeners = ['refreshScheduledBackups']; | ||||
| 
 | ||||
|     protected $queryString = ['selectedBackupId']; | ||||
| @@ -49,6 +51,14 @@ class ScheduledBackups extends Component | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function setCustomType() | ||||
|     { | ||||
|         $this->database->custom_type = $this->custom_type; | ||||
|         $this->database->save(); | ||||
|         $this->dispatch('success', 'Database type set.'); | ||||
|         $this->refreshScheduledBackups(); | ||||
|     } | ||||
| 
 | ||||
|     public function delete($scheduled_backup_id): void | ||||
|     { | ||||
|         $this->database->scheduledBackups->find($scheduled_backup_id)->delete(); | ||||
| @@ -62,5 +72,6 @@ class ScheduledBackups extends Component | ||||
|         if ($id) { | ||||
|             $this->setSelectedBackup($id); | ||||
|         } | ||||
|         $this->dispatch('refreshScheduledBackups'); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -35,6 +35,6 @@ class Index extends Component | ||||
|     { | ||||
|         $project = collect($this->projects)->firstWhere('uuid', $projectUuid); | ||||
| 
 | ||||
|         return $this->redirect($project->navigateTo(), true); | ||||
|         return $this->redirect($project->navigateTo(), navigate: false); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -31,8 +31,9 @@ class Configuration extends Component | ||||
| 
 | ||||
|         return [ | ||||
|             "echo-private:user.{$userId},ServiceStatusChanged" => 'check_status', | ||||
|             'check_status', | ||||
|             'refreshStatus' => '$refresh', | ||||
|             'check_status', | ||||
|             'refreshServices', | ||||
|         ]; | ||||
|     } | ||||
| 
 | ||||
| @@ -63,6 +64,13 @@ class Configuration extends Component | ||||
|         $this->databases = $this->service->databases->sort(); | ||||
|     } | ||||
| 
 | ||||
|     public function refreshServices() | ||||
|     { | ||||
|         $this->service->refresh(); | ||||
|         $this->applications = $this->service->applications->sort(); | ||||
|         $this->databases = $this->service->databases->sort(); | ||||
|     } | ||||
| 
 | ||||
|     public function restartApplication($id) | ||||
|     { | ||||
|         try { | ||||
|   | ||||
| @@ -7,6 +7,7 @@ use App\Actions\Database\StopDatabaseProxy; | ||||
| use App\Models\InstanceSettings; | ||||
| use App\Models\ServiceDatabase; | ||||
| use Illuminate\Support\Facades\Auth; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Hash; | ||||
| use Livewire\Component; | ||||
| 
 | ||||
| @@ -83,6 +84,42 @@ class Database extends Component | ||||
|         $this->dispatch('success', 'You need to restart the service for the changes to take effect.'); | ||||
|     } | ||||
| 
 | ||||
|     public function convertToApplication() | ||||
|     { | ||||
|         try { | ||||
|             $service = $this->database->service; | ||||
|             $serviceDatabase = $this->database; | ||||
| 
 | ||||
|             // Check if application with same name already exists
 | ||||
|             if ($service->applications()->where('name', $serviceDatabase->name)->exists()) { | ||||
|                 throw new \Exception('An application with this name already exists.'); | ||||
|             } | ||||
| 
 | ||||
|             // Create new parameters removing database_uuid
 | ||||
|             $redirectParams = collect($this->parameters) | ||||
|                 ->except('database_uuid') | ||||
|                 ->all(); | ||||
| 
 | ||||
|             DB::transaction(function () use ($service, $serviceDatabase) { | ||||
|                 $service->applications()->create([ | ||||
|                     'name' => $serviceDatabase->name, | ||||
|                     'human_name' => $serviceDatabase->human_name, | ||||
|                     'description' => $serviceDatabase->description, | ||||
|                     'exclude_from_status' => $serviceDatabase->exclude_from_status, | ||||
|                     'is_log_drain_enabled' => $serviceDatabase->is_log_drain_enabled, | ||||
|                     'image' => $serviceDatabase->image, | ||||
|                     'service_id' => $service->id, | ||||
|                     'is_migrated' => true, | ||||
|                 ]); | ||||
|                 $serviceDatabase->delete(); | ||||
|             }); | ||||
| 
 | ||||
|             return redirect()->route('project.service.configuration', $redirectParams); | ||||
|         } catch (\Throwable $e) { | ||||
|             return handleError($e, $this); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function instantSave() | ||||
|     { | ||||
|         if ($this->database->is_public && ! $this->database->public_port) { | ||||
|   | ||||
| @@ -24,7 +24,7 @@ class Index extends Component | ||||
| 
 | ||||
|     public $s3s; | ||||
| 
 | ||||
|     protected $listeners = ['generateDockerCompose']; | ||||
|     protected $listeners = ['generateDockerCompose', 'refreshScheduledBackups' => '$refresh']; | ||||
| 
 | ||||
|     public function mount() | ||||
|     { | ||||
|   | ||||
| @@ -5,6 +5,7 @@ namespace App\Livewire\Project\Service; | ||||
| use App\Models\InstanceSettings; | ||||
| use App\Models\ServiceApplication; | ||||
| use Illuminate\Support\Facades\Auth; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Hash; | ||||
| use Livewire\Component; | ||||
| use Spatie\Url\Url; | ||||
| @@ -23,7 +24,7 @@ class ServiceApplicationView extends Component | ||||
|         'application.human_name' => 'nullable', | ||||
|         'application.description' => 'nullable', | ||||
|         'application.fqdn' => 'nullable', | ||||
|         'application.image' => 'required', | ||||
|         'application.image' => 'string|nullable', | ||||
|         'application.exclude_from_status' => 'required|boolean', | ||||
|         'application.required_fqdn' => 'required|boolean', | ||||
|         'application.is_log_drain_enabled' => 'nullable|boolean', | ||||
| @@ -73,6 +74,40 @@ class ServiceApplicationView extends Component | ||||
|         $this->parameters = get_route_parameters(); | ||||
|     } | ||||
| 
 | ||||
|     public function convertToDatabase() | ||||
|     { | ||||
|         try { | ||||
|             $service = $this->application->service; | ||||
|             $serviceApplication = $this->application; | ||||
| 
 | ||||
|             // Check if database with same name already exists
 | ||||
|             if ($service->databases()->where('name', $serviceApplication->name)->exists()) { | ||||
|                 throw new \Exception('A database with this name already exists.'); | ||||
|             } | ||||
| 
 | ||||
|             $redirectParams = collect($this->parameters) | ||||
|                 ->except('database_uuid') | ||||
|                 ->all(); | ||||
|             DB::transaction(function () use ($service, $serviceApplication) { | ||||
|                 $service->databases()->create([ | ||||
|                     'name' => $serviceApplication->name, | ||||
|                     'human_name' => $serviceApplication->human_name, | ||||
|                     'description' => $serviceApplication->description, | ||||
|                     'exclude_from_status' => $serviceApplication->exclude_from_status, | ||||
|                     'is_log_drain_enabled' => $serviceApplication->is_log_drain_enabled, | ||||
|                     'image' => $serviceApplication->image, | ||||
|                     'service_id' => $service->id, | ||||
|                     'is_migrated' => true, | ||||
|                 ]); | ||||
|                 $serviceApplication->delete(); | ||||
|             }); | ||||
| 
 | ||||
|             return redirect()->route('project.service.configuration', $redirectParams); | ||||
|         } catch (\Throwable $e) { | ||||
|             return handleError($e, $this); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function submit() | ||||
|     { | ||||
|         try { | ||||
|   | ||||
| @@ -82,6 +82,7 @@ class StackForm extends Component | ||||
|             $this->service->refresh(); | ||||
|             $this->service->saveComposeConfigs(); | ||||
|             $this->dispatch('refreshEnvs'); | ||||
|             $this->dispatch('refreshServices'); | ||||
|             $notify && $this->dispatch('success', 'Service saved.'); | ||||
|         } catch (\Throwable $e) { | ||||
|             return handleError($e, $this); | ||||
|   | ||||
| @@ -9,9 +9,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute; | ||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||
| use Illuminate\Database\Eloquent\Relations\HasMany; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| use Illuminate\Process\InvokedProcess; | ||||
| use Illuminate\Support\Collection; | ||||
| use Illuminate\Support\Facades\Process; | ||||
| use Illuminate\Support\Facades\Validator; | ||||
| use Illuminate\Support\Str; | ||||
| use OpenApi\Attributes as OA; | ||||
| @@ -270,51 +268,17 @@ class Application extends BaseModel | ||||
|         return $containers->pluck('Names')->toArray(); | ||||
|     } | ||||
| 
 | ||||
|     public function stopContainers(array $containerNames, $server, int $timeout = 600) | ||||
|     { | ||||
|         $processes = []; | ||||
|         foreach ($containerNames as $containerName) { | ||||
|             $processes[$containerName] = $this->stopContainer($containerName, $server, $timeout); | ||||
|         } | ||||
| 
 | ||||
|         $startTime = time(); | ||||
|         while (count($processes) > 0) { | ||||
|             $finishedProcesses = array_filter($processes, function ($process) { | ||||
|                 return ! $process->running(); | ||||
|             }); | ||||
|             foreach ($finishedProcesses as $containerName => $process) { | ||||
|                 unset($processes[$containerName]); | ||||
|                 $this->removeContainer($containerName, $server); | ||||
|             } | ||||
| 
 | ||||
|             if (time() - $startTime >= $timeout) { | ||||
|                 $this->forceStopRemainingContainers(array_keys($processes), $server); | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             usleep(100000); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function stopContainer(string $containerName, $server, int $timeout): InvokedProcess | ||||
|     { | ||||
|         return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName"); | ||||
|     } | ||||
| 
 | ||||
|     public function removeContainer(string $containerName, $server) | ||||
|     { | ||||
|         instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false); | ||||
|     } | ||||
| 
 | ||||
|     public function forceStopRemainingContainers(array $containerNames, $server) | ||||
|     public function stopContainers(array $containerNames, $server, int $timeout = 30) | ||||
|     { | ||||
|         foreach ($containerNames as $containerName) { | ||||
|             instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false); | ||||
|             $this->removeContainer($containerName, $server); | ||||
|             instant_remote_process(command: [ | ||||
|                 "docker stop --time=$timeout $containerName", | ||||
|                 "docker rm -f $containerName", | ||||
|             ], server: $server, throwError: false); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function delete_configurations() | ||||
|     public function deleteConfigurations() | ||||
|     { | ||||
|         $server = data_get($this, 'destination.server'); | ||||
|         $workdir = $this->workdir(); | ||||
| @@ -323,8 +287,9 @@ class Application extends BaseModel | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function delete_volumes(?Collection $persistentStorages) | ||||
|     public function deleteVolumes() | ||||
|     { | ||||
|         $persistentStorages = $this->persistentStorages()->get() ?? collect(); | ||||
|         if ($this->build_pack === 'dockercompose') { | ||||
|             $server = data_get($this, 'destination.server'); | ||||
|             instant_remote_process(["cd {$this->dirOnServer()} && docker compose down -v"], $server, false); | ||||
| @@ -339,8 +304,9 @@ class Application extends BaseModel | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function delete_connected_networks($uuid) | ||||
|     public function deleteConnectedNetworks() | ||||
|     { | ||||
|         $uuid = $this->uuid; | ||||
|         $server = data_get($this, 'destination.server'); | ||||
|         instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false); | ||||
|         instant_remote_process(["docker network rm {$uuid}"], $server, false); | ||||
|   | ||||
| @@ -488,7 +488,7 @@ $schema://$host { | ||||
|         $proxy_path = "$base_path/proxy"; | ||||
| 
 | ||||
|         if ($proxyType === ProxyTypes::TRAEFIK->value) { | ||||
|             $proxy_path = '/'; | ||||
|             $proxy_path = $proxy_path.'/'; | ||||
|         } elseif ($proxyType === ProxyTypes::CADDY->value) { | ||||
|             $proxy_path = $proxy_path.'/caddy'; | ||||
|         } elseif ($proxyType === ProxyTypes::NGINX->value) { | ||||
|   | ||||
| @@ -6,9 +6,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute; | ||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||
| use Illuminate\Database\Eloquent\Relations\HasMany; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| use Illuminate\Process\InvokedProcess; | ||||
| use Illuminate\Support\Collection; | ||||
| use Illuminate\Support\Facades\Process; | ||||
| use Illuminate\Support\Facades\Storage; | ||||
| use OpenApi\Attributes as OA; | ||||
| use Spatie\Url\Url; | ||||
| @@ -158,51 +156,17 @@ class Service extends BaseModel | ||||
|         return $containersToStop; | ||||
|     } | ||||
| 
 | ||||
|     public function stopContainers(array $containerNames, $server, int $timeout = 300) | ||||
|     { | ||||
|         $processes = []; | ||||
|         foreach ($containerNames as $containerName) { | ||||
|             $processes[$containerName] = $this->stopContainer($containerName, $timeout); | ||||
|         } | ||||
| 
 | ||||
|         $startTime = time(); | ||||
|         while (count($processes) > 0) { | ||||
|             $finishedProcesses = array_filter($processes, function ($process) { | ||||
|                 return ! $process->running(); | ||||
|             }); | ||||
|             foreach (array_keys($finishedProcesses) as $containerName) { | ||||
|                 unset($processes[$containerName]); | ||||
|                 $this->removeContainer($containerName, $server); | ||||
|             } | ||||
| 
 | ||||
|             if (time() - $startTime >= $timeout) { | ||||
|                 $this->forceStopRemainingContainers(array_keys($processes), $server); | ||||
|                 break; | ||||
|             } | ||||
| 
 | ||||
|             usleep(100000); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function stopContainer(string $containerName, int $timeout): InvokedProcess | ||||
|     { | ||||
|         return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName"); | ||||
|     } | ||||
| 
 | ||||
|     public function removeContainer(string $containerName, $server) | ||||
|     { | ||||
|         instant_remote_process(command: ["docker rm -f $containerName"], server: $server, throwError: false); | ||||
|     } | ||||
| 
 | ||||
|     public function forceStopRemainingContainers(array $containerNames, $server) | ||||
|     public function stopContainers(array $containerNames, $server, int $timeout = 30) | ||||
|     { | ||||
|         foreach ($containerNames as $containerName) { | ||||
|             instant_remote_process(command: ["docker kill $containerName"], server: $server, throwError: false); | ||||
|             $this->removeContainer($containerName, $server); | ||||
|             instant_remote_process(command: [ | ||||
|                 "docker stop --time=$timeout $containerName", | ||||
|                 "docker rm -f $containerName", | ||||
|             ], server: $server, throwError: false); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function delete_configurations() | ||||
|     public function deleteConfigurations() | ||||
|     { | ||||
|         $server = data_get($this, 'destination.server'); | ||||
|         $workdir = $this->workdir(); | ||||
| @@ -211,11 +175,11 @@ class Service extends BaseModel | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function delete_connected_networks($uuid) | ||||
|     public function deleteConnectedNetworks() | ||||
|     { | ||||
|         $server = data_get($this, 'destination.server'); | ||||
|         instant_remote_process(["docker network disconnect {$uuid} coolify-proxy"], $server, false); | ||||
|         instant_remote_process(["docker network rm {$uuid}"], $server, false); | ||||
|         instant_remote_process(["docker network disconnect {$this->uuid} coolify-proxy"], $server, false); | ||||
|         instant_remote_process(["docker network rm {$this->uuid}"], $server, false); | ||||
|     } | ||||
| 
 | ||||
|     public function getStatusAttribute() | ||||
|   | ||||
| @@ -16,6 +16,7 @@ class ServiceDatabase extends BaseModel | ||||
|         static::deleting(function ($service) { | ||||
|             $service->persistentStorages()->delete(); | ||||
|             $service->fileStorages()->delete(); | ||||
|             $service->scheduledBackups()->delete(); | ||||
|         }); | ||||
|         static::saving(function ($service) { | ||||
|             if ($service->isDirty('status')) { | ||||
| @@ -77,6 +78,9 @@ class ServiceDatabase extends BaseModel | ||||
| 
 | ||||
|     public function databaseType() | ||||
|     { | ||||
|         if (filled($this->custom_type)) { | ||||
|             return 'standalone-'.$this->custom_type; | ||||
|         } | ||||
|         $image = str($this->image)->before(':'); | ||||
|         if ($image->contains('supabase/postgres')) { | ||||
|             $finalImage = 'supabase/postgres'; | ||||
| @@ -141,6 +145,7 @@ class ServiceDatabase extends BaseModel | ||||
|             str($this->databaseType())->contains('postgres') || | ||||
|             str($this->databaseType())->contains('postgis') || | ||||
|             str($this->databaseType())->contains('mariadb') || | ||||
|             str($this->databaseType())->contains('mongo'); | ||||
|             str($this->databaseType())->contains('mongo') || | ||||
|             filled($this->custom_type); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | ||||
| use Illuminate\Database\Eloquent\Collection; | ||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| 
 | ||||
| @@ -94,7 +93,7 @@ class StandaloneClickhouse extends BaseModel | ||||
|         return database_configuration_dir()."/{$this->uuid}"; | ||||
|     } | ||||
| 
 | ||||
|     public function delete_configurations() | ||||
|     public function deleteConfigurations() | ||||
|     { | ||||
|         $server = data_get($this, 'destination.server'); | ||||
|         $workdir = $this->workdir(); | ||||
| @@ -103,8 +102,9 @@ class StandaloneClickhouse extends BaseModel | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function delete_volumes(Collection $persistentStorages) | ||||
|     public function deleteVolumes() | ||||
|     { | ||||
|         $persistentStorages = $this->persistentStorages()->get() ?? collect(); | ||||
|         if ($persistentStorages->count() === 0) { | ||||
|             return; | ||||
|         } | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | ||||
| use Illuminate\Database\Eloquent\Collection; | ||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| 
 | ||||
| @@ -94,7 +93,7 @@ class StandaloneDragonfly extends BaseModel | ||||
|         return database_configuration_dir()."/{$this->uuid}"; | ||||
|     } | ||||
| 
 | ||||
|     public function delete_configurations() | ||||
|     public function deleteConfigurations() | ||||
|     { | ||||
|         $server = data_get($this, 'destination.server'); | ||||
|         $workdir = $this->workdir(); | ||||
| @@ -103,8 +102,9 @@ class StandaloneDragonfly extends BaseModel | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function delete_volumes(Collection $persistentStorages) | ||||
|     public function deleteVolumes() | ||||
|     { | ||||
|         $persistentStorages = $this->persistentStorages()->get() ?? collect(); | ||||
|         if ($persistentStorages->count() === 0) { | ||||
|             return; | ||||
|         } | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | ||||
| use Illuminate\Database\Eloquent\Collection; | ||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| 
 | ||||
| @@ -94,7 +93,7 @@ class StandaloneKeydb extends BaseModel | ||||
|         return database_configuration_dir()."/{$this->uuid}"; | ||||
|     } | ||||
| 
 | ||||
|     public function delete_configurations() | ||||
|     public function deleteConfigurations() | ||||
|     { | ||||
|         $server = data_get($this, 'destination.server'); | ||||
|         $workdir = $this->workdir(); | ||||
| @@ -103,8 +102,9 @@ class StandaloneKeydb extends BaseModel | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function delete_volumes(Collection $persistentStorages) | ||||
|     public function deleteVolumes() | ||||
|     { | ||||
|         $persistentStorages = $this->persistentStorages()->get() ?? collect(); | ||||
|         if ($persistentStorages->count() === 0) { | ||||
|             return; | ||||
|         } | ||||
|   | ||||
| @@ -3,8 +3,8 @@ | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | ||||
| use Illuminate\Database\Eloquent\Collection; | ||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||
| use Illuminate\Database\Eloquent\Relations\MorphTo; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| 
 | ||||
| class StandaloneMariadb extends BaseModel | ||||
| @@ -94,7 +94,7 @@ class StandaloneMariadb extends BaseModel | ||||
|         return database_configuration_dir()."/{$this->uuid}"; | ||||
|     } | ||||
| 
 | ||||
|     public function delete_configurations() | ||||
|     public function deleteConfigurations() | ||||
|     { | ||||
|         $server = data_get($this, 'destination.server'); | ||||
|         $workdir = $this->workdir(); | ||||
| @@ -103,8 +103,9 @@ class StandaloneMariadb extends BaseModel | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function delete_volumes(Collection $persistentStorages) | ||||
|     public function deleteVolumes() | ||||
|     { | ||||
|         $persistentStorages = $this->persistentStorages()->get() ?? collect(); | ||||
|         if ($persistentStorages->count() === 0) { | ||||
|             return; | ||||
|         } | ||||
| @@ -253,7 +254,7 @@ class StandaloneMariadb extends BaseModel | ||||
|         return $this->morphMany(LocalFileVolume::class, 'resource'); | ||||
|     } | ||||
| 
 | ||||
|     public function destination() | ||||
|     public function destination(): MorphTo | ||||
|     { | ||||
|         return $this->morphTo(); | ||||
|     } | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | ||||
| use Illuminate\Database\Eloquent\Collection; | ||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| 
 | ||||
| @@ -98,7 +97,7 @@ class StandaloneMongodb extends BaseModel | ||||
|         return database_configuration_dir()."/{$this->uuid}"; | ||||
|     } | ||||
| 
 | ||||
|     public function delete_configurations() | ||||
|     public function deleteConfigurations() | ||||
|     { | ||||
|         $server = data_get($this, 'destination.server'); | ||||
|         $workdir = $this->workdir(); | ||||
| @@ -107,8 +106,9 @@ class StandaloneMongodb extends BaseModel | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function delete_volumes(Collection $persistentStorages) | ||||
|     public function deleteVolumes() | ||||
|     { | ||||
|         $persistentStorages = $this->persistentStorages()->get() ?? collect(); | ||||
|         if ($persistentStorages->count() === 0) { | ||||
|             return; | ||||
|         } | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | ||||
| use Illuminate\Database\Eloquent\Collection; | ||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| 
 | ||||
| @@ -95,7 +94,7 @@ class StandaloneMysql extends BaseModel | ||||
|         return database_configuration_dir()."/{$this->uuid}"; | ||||
|     } | ||||
| 
 | ||||
|     public function delete_configurations() | ||||
|     public function deleteConfigurations() | ||||
|     { | ||||
|         $server = data_get($this, 'destination.server'); | ||||
|         $workdir = $this->workdir(); | ||||
| @@ -104,8 +103,9 @@ class StandaloneMysql extends BaseModel | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function delete_volumes(Collection $persistentStorages) | ||||
|     public function deleteVolumes() | ||||
|     { | ||||
|         $persistentStorages = $this->persistentStorages()->get() ?? collect(); | ||||
|         if ($persistentStorages->count() === 0) { | ||||
|             return; | ||||
|         } | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | ||||
| use Illuminate\Database\Eloquent\Collection; | ||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| 
 | ||||
| @@ -59,7 +58,7 @@ class StandalonePostgresql extends BaseModel | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
|     public function delete_configurations() | ||||
|     public function deleteConfigurations() | ||||
|     { | ||||
|         $server = data_get($this, 'destination.server'); | ||||
|         $workdir = $this->workdir(); | ||||
| @@ -68,8 +67,9 @@ class StandalonePostgresql extends BaseModel | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function delete_volumes(Collection $persistentStorages) | ||||
|     public function deleteVolumes() | ||||
|     { | ||||
|         $persistentStorages = $this->persistentStorages()->get() ?? collect(); | ||||
|         if ($persistentStorages->count() === 0) { | ||||
|             return; | ||||
|         } | ||||
|   | ||||
| @@ -3,7 +3,6 @@ | ||||
| namespace App\Models; | ||||
| 
 | ||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | ||||
| use Illuminate\Database\Eloquent\Collection; | ||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||
| use Illuminate\Database\Eloquent\SoftDeletes; | ||||
| 
 | ||||
| @@ -96,7 +95,7 @@ class StandaloneRedis extends BaseModel | ||||
|         return database_configuration_dir()."/{$this->uuid}"; | ||||
|     } | ||||
| 
 | ||||
|     public function delete_configurations() | ||||
|     public function deleteConfigurations() | ||||
|     { | ||||
|         $server = data_get($this, 'destination.server'); | ||||
|         $workdir = $this->workdir(); | ||||
| @@ -105,8 +104,9 @@ class StandaloneRedis extends BaseModel | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     public function delete_volumes(Collection $persistentStorages) | ||||
|     public function deleteVolumes() | ||||
|     { | ||||
|         $persistentStorages = $this->persistentStorages()->get() ?? collect(); | ||||
|         if ($persistentStorages->count() === 0) { | ||||
|             return; | ||||
|         } | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| 
 | ||||
| namespace App\Traits; | ||||
| 
 | ||||
| use DB; | ||||
| use Illuminate\Support\Facades\DB; | ||||
| use Illuminate\Support\Facades\Session; | ||||
| 
 | ||||
| trait DeletesUserSessions | ||||
|   | ||||
| @@ -28,6 +28,7 @@ function queue_application_deployment(Application $application, string $deployme | ||||
|     // Check if there's already a deployment in progress or queued for this application and commit
 | ||||
|     $existing_deployment = ApplicationDeploymentQueue::where('application_id', $application_id) | ||||
|         ->where('commit', $commit) | ||||
|         ->where('pull_request_id', $pull_request_id) | ||||
|         ->whereIn('status', [ApplicationDeploymentStatus::IN_PROGRESS->value, ApplicationDeploymentStatus::QUEUED->value]) | ||||
|         ->first(); | ||||
| 
 | ||||
|   | ||||
| @@ -2990,12 +2990,12 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int | ||||
|                 $applicationFound = ServiceApplication::where('name', $serviceName)->where('service_id', $resource->id)->first(); | ||||
|                 if ($applicationFound) { | ||||
|                     $savedService = $applicationFound; | ||||
|                     $savedService = ServiceDatabase::firstOrCreate([ | ||||
|                         'name' => $applicationFound->name, | ||||
|                         'image' => $applicationFound->image, | ||||
|                         'service_id' => $applicationFound->service_id, | ||||
|                     ]); | ||||
|                     $applicationFound->delete(); | ||||
|                     // $savedService = ServiceDatabase::firstOrCreate([
 | ||||
|                     //     'name' => $applicationFound->name,
 | ||||
|                     //     'image' => $applicationFound->image,
 | ||||
|                     //     'service_id' => $applicationFound->service_id,
 | ||||
|                     // ]);
 | ||||
|                     // $applicationFound->delete();
 | ||||
|                 } else { | ||||
|                     $savedService = ServiceDatabase::firstOrCreate([ | ||||
|                         'name' => $serviceName, | ||||
| @@ -3248,12 +3248,12 @@ function newParser(Application|Service $resource, int $pull_request_id = 0, ?int | ||||
|                 $applicationFound = ServiceApplication::where('name', $serviceName)->where('image', $image)->where('service_id', $resource->id)->first(); | ||||
|                 if ($applicationFound) { | ||||
|                     $savedService = $applicationFound; | ||||
|                     $savedService = ServiceDatabase::firstOrCreate([ | ||||
|                         'name' => $applicationFound->name, | ||||
|                         'image' => $applicationFound->image, | ||||
|                         'service_id' => $applicationFound->service_id, | ||||
|                     ]); | ||||
|                     $applicationFound->delete(); | ||||
|                     // $savedService = ServiceDatabase::firstOrCreate([
 | ||||
|                     //     'name' => $applicationFound->name,
 | ||||
|                     //     'image' => $applicationFound->image,
 | ||||
|                     //     'service_id' => $applicationFound->service_id,
 | ||||
|                     // ]);
 | ||||
|                     // $applicationFound->delete();
 | ||||
|                 } else { | ||||
|                     $savedService = ServiceDatabase::firstOrCreate([ | ||||
|                         'name' => $serviceName, | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| 
 | ||||
| return [ | ||||
|     'coolify' => [ | ||||
|         'version' => '4.0.0-beta.413', | ||||
|         'version' => '4.0.0-beta.418', | ||||
|         'helper_version' => '1.0.8', | ||||
|         'realtime_version' => '1.0.8', | ||||
|         'self_hosted' => env('SELF_HOSTED', true), | ||||
|   | ||||
| @@ -0,0 +1,36 @@ | ||||
| <?php | ||||
| 
 | ||||
| use Illuminate\Database\Migrations\Migration; | ||||
| use Illuminate\Database\Schema\Blueprint; | ||||
| use Illuminate\Support\Facades\Schema; | ||||
| 
 | ||||
| return new class extends Migration | ||||
| { | ||||
|     /** | ||||
|      * Run the migrations. | ||||
|      */ | ||||
|     public function up(): void | ||||
|     { | ||||
|         Schema::table('service_applications', function (Blueprint $table) { | ||||
|             $table->boolean('is_migrated')->default(false); | ||||
|         }); | ||||
|         Schema::table('service_databases', function (Blueprint $table) { | ||||
|             $table->boolean('is_migrated')->default(false); | ||||
|             $table->string('custom_type')->nullable(); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Reverse the migrations. | ||||
|      */ | ||||
|     public function down(): void | ||||
|     { | ||||
|         Schema::table('service_applications', function (Blueprint $table) { | ||||
|             $table->dropColumn('is_migrated'); | ||||
|         }); | ||||
|         Schema::table('service_databases', function (Blueprint $table) { | ||||
|             $table->dropColumn('is_migrated'); | ||||
|             $table->dropColumn('custom_type'); | ||||
|         }); | ||||
|     } | ||||
| }; | ||||
| @@ -19,7 +19,7 @@ class ApplicationSeeder extends Seeder | ||||
|             'fqdn' => 'http://nodejs.127.0.0.1.sslip.io', | ||||
|             'repository_project_id' => 603035348, | ||||
|             'git_repository' => 'coollabsio/coolify-examples', | ||||
|             'git_branch' => 'main', | ||||
|             'git_branch' => 'v4.x', | ||||
|             'base_directory' => '/nodejs', | ||||
|             'build_pack' => 'nixpacks', | ||||
|             'ports_exposes' => '3000', | ||||
| @@ -34,7 +34,7 @@ class ApplicationSeeder extends Seeder | ||||
|             'fqdn' => 'http://dockerfile.127.0.0.1.sslip.io', | ||||
|             'repository_project_id' => 603035348, | ||||
|             'git_repository' => 'coollabsio/coolify-examples', | ||||
|             'git_branch' => 'main', | ||||
|             'git_branch' => 'v4.x', | ||||
|             'base_directory' => '/dockerfile', | ||||
|             'build_pack' => 'dockerfile', | ||||
|             'ports_exposes' => '80', | ||||
| @@ -48,7 +48,7 @@ class ApplicationSeeder extends Seeder | ||||
|             'name' => 'Pure Dockerfile Example', | ||||
|             'fqdn' => 'http://pure-dockerfile.127.0.0.1.sslip.io', | ||||
|             'git_repository' => 'coollabsio/coolify', | ||||
|             'git_branch' => 'main', | ||||
|             'git_branch' => 'v4.x', | ||||
|             'git_commit_sha' => 'HEAD', | ||||
|             'build_pack' => 'dockerfile', | ||||
|             'ports_exposes' => '80', | ||||
|   | ||||
| @@ -61,7 +61,7 @@ services: | ||||
|       retries: 10 | ||||
|       timeout: 2s | ||||
|   soketi: | ||||
|     image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.6' | ||||
|     image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.8' | ||||
|     ports: | ||||
|       - "${SOKETI_PORT:-6001}:6001" | ||||
|       - "6002:6002" | ||||
|   | ||||
| @@ -61,7 +61,7 @@ services: | ||||
|       retries: 10 | ||||
|       timeout: 2s | ||||
|   soketi: | ||||
|     image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.6' | ||||
|       image: '${REGISTRY_URL:-ghcr.io}/coollabsio/coolify-realtime:1.0.8' | ||||
|     ports: | ||||
|       - "${SOKETI_PORT:-6001}:6001" | ||||
|       - "6002:6002" | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| { | ||||
|     "coolify": { | ||||
|         "v4": { | ||||
|             "version": "4.0.0-beta.413" | ||||
|             "version": "4.0.0-beta.418" | ||||
|         }, | ||||
|         "nightly": { | ||||
|             "version": "4.0.0-beta.414" | ||||
|             "version": "4.0.0-beta.419" | ||||
|         }, | ||||
|         "helper": { | ||||
|             "version": "1.0.8" | ||||
|   | ||||
							
								
								
									
										86
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										86
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							| @@ -22,7 +22,7 @@ | ||||
|                 "pusher-js": "8.4.0", | ||||
|                 "tailwind-scrollbar": "^3.1.0", | ||||
|                 "tailwindcss": "3.4.17", | ||||
|                 "vite": "^6.2.6", | ||||
|                 "vite": "^6.3.4", | ||||
|                 "vue": "3.5.13" | ||||
|             } | ||||
|         }, | ||||
| @@ -2931,6 +2931,51 @@ | ||||
|                 "node": ">=0.8" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/tinyglobby": { | ||||
|             "version": "0.2.13", | ||||
|             "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.13.tgz", | ||||
|             "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "fdir": "^6.4.4", | ||||
|                 "picomatch": "^4.0.2" | ||||
|             }, | ||||
|             "engines": { | ||||
|                 "node": ">=12.0.0" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "url": "https://github.com/sponsors/SuperchupuDev" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/tinyglobby/node_modules/fdir": { | ||||
|             "version": "6.4.4", | ||||
|             "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", | ||||
|             "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "peerDependencies": { | ||||
|                 "picomatch": "^3 || ^4" | ||||
|             }, | ||||
|             "peerDependenciesMeta": { | ||||
|                 "picomatch": { | ||||
|                     "optional": true | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/tinyglobby/node_modules/picomatch": { | ||||
|             "version": "4.0.2", | ||||
|             "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", | ||||
|             "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "url": "https://github.com/sponsors/jonschlinkert" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/to-regex-range": { | ||||
|             "version": "5.0.1", | ||||
|             "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", | ||||
| @@ -2994,15 +3039,18 @@ | ||||
|             "license": "MIT" | ||||
|         }, | ||||
|         "node_modules/vite": { | ||||
|             "version": "6.2.6", | ||||
|             "resolved": "https://registry.npmjs.org/vite/-/vite-6.2.6.tgz", | ||||
|             "integrity": "sha512-9xpjNl3kR4rVDZgPNdTL0/c6ao4km69a/2ihNQbcANz8RuCOK3hQBmLSJf3bRKVQjVMda+YvizNE8AwvogcPbw==", | ||||
|             "version": "6.3.4", | ||||
|             "resolved": "https://registry.npmjs.org/vite/-/vite-6.3.4.tgz", | ||||
|             "integrity": "sha512-BiReIiMS2fyFqbqNT/Qqt4CVITDU9M9vE+DKcVAsB+ZV0wvTKd+3hMbkpxz1b+NmEDMegpVbisKiAZOnvO92Sw==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "dependencies": { | ||||
|                 "esbuild": "^0.25.0", | ||||
|                 "fdir": "^6.4.4", | ||||
|                 "picomatch": "^4.0.2", | ||||
|                 "postcss": "^8.5.3", | ||||
|                 "rollup": "^4.30.1" | ||||
|                 "rollup": "^4.34.9", | ||||
|                 "tinyglobby": "^0.2.13" | ||||
|             }, | ||||
|             "bin": { | ||||
|                 "vite": "bin/vite.js" | ||||
| @@ -3076,6 +3124,34 @@ | ||||
|                 "picomatch": "^2.3.1" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/vite/node_modules/fdir": { | ||||
|             "version": "6.4.4", | ||||
|             "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.4.tgz", | ||||
|             "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "peerDependencies": { | ||||
|                 "picomatch": "^3 || ^4" | ||||
|             }, | ||||
|             "peerDependenciesMeta": { | ||||
|                 "picomatch": { | ||||
|                     "optional": true | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/vite/node_modules/picomatch": { | ||||
|             "version": "4.0.2", | ||||
|             "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", | ||||
|             "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", | ||||
|             "dev": true, | ||||
|             "license": "MIT", | ||||
|             "engines": { | ||||
|                 "node": ">=12" | ||||
|             }, | ||||
|             "funding": { | ||||
|                 "url": "https://github.com/sponsors/jonschlinkert" | ||||
|             } | ||||
|         }, | ||||
|         "node_modules/vue": { | ||||
|             "version": "3.5.13", | ||||
|             "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.13.tgz", | ||||
|   | ||||
| @@ -16,7 +16,7 @@ | ||||
|         "pusher-js": "8.4.0", | ||||
|         "tailwind-scrollbar": "^3.1.0", | ||||
|         "tailwindcss": "3.4.17", | ||||
|         "vite": "^6.2.6", | ||||
|         "vite": "^6.3.4", | ||||
|         "vue": "3.5.13" | ||||
|     }, | ||||
|     "dependencies": { | ||||
|   | ||||
| @@ -123,7 +123,7 @@ | ||||
|             <ul role="list" class="flex flex-col h-full space-y-1.5"> | ||||
|                 @if (isSubscribed() || !isCloud()) | ||||
|                     <li> | ||||
|                         <a title="Dashboard" href="/" wire:navigate | ||||
|                         <a title="Dashboard" href="/" | ||||
|                             class="{{ request()->is('/') ? 'menu-item-active menu-item' : 'menu-item' }}"> | ||||
|                             <svg xmlns="http://www.w3.org/2000/svg" class="icon" fill="none" viewBox="0 0 24 24" | ||||
|                                 stroke="currentColor"> | ||||
| @@ -134,7 +134,7 @@ | ||||
|                         </a> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <a title="Projects" wire:navigate | ||||
|                         <a title="Projects" | ||||
|                             class="{{ request()->is('project/*') || request()->is('projects') ? 'menu-item menu-item-active' : 'menu-item' }}" | ||||
|                             href="/projects"> | ||||
|                             <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" | ||||
| @@ -149,7 +149,7 @@ | ||||
|                         </a> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <a title="Servers" wire:navigate | ||||
|                         <a title="Servers" | ||||
|                             class="{{ request()->is('server/*') || request()->is('servers') ? 'menu-item menu-item-active' : 'menu-item' }}" | ||||
|                             href="/servers"> | ||||
|                             <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24" | ||||
| @@ -168,7 +168,7 @@ | ||||
|                     </li> | ||||
| 
 | ||||
|                     <li> | ||||
|                         <a title="Sources" wire:navigate | ||||
|                         <a title="Sources" | ||||
|                             class="{{ request()->is('source*') ? 'menu-item-active menu-item' : 'menu-item' }}" | ||||
|                             href="{{ route('source.all') }}"> | ||||
|                             <svg class="icon" viewBox="0 0 15 15" xmlns="http://www.w3.org/2000/svg"> | ||||
| @@ -179,7 +179,7 @@ | ||||
|                         </a> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <a title="Destinations" wire:navigate | ||||
|                         <a title="Destinations" | ||||
|                             class="{{ request()->is('destination*') ? 'menu-item-active menu-item' : 'menu-item' }}" | ||||
|                             href="{{ route('destination.index') }}"> | ||||
| 
 | ||||
| @@ -192,7 +192,7 @@ | ||||
|                         </a> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <a title="S3 Storages" wire:navigate | ||||
|                         <a title="S3 Storages" | ||||
|                             class="{{ request()->is('storages*') ? 'menu-item-active menu-item' : 'menu-item' }}" | ||||
|                             href="{{ route('storage.index') }}"> | ||||
|                             <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"> | ||||
| @@ -207,7 +207,7 @@ | ||||
|                         </a> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <a title="Shared variables" wire:navigate | ||||
|                         <a title="Shared variables" | ||||
|                             class="{{ request()->is('shared-variables*') ? 'menu-item-active menu-item' : 'menu-item' }}" | ||||
|                             href="{{ route('shared-variables.index') }}"> | ||||
|                             <svg xmlns="http://www.w3.org/2000/svg" class="icon" viewBox="0 0 24 24"> | ||||
| @@ -222,7 +222,7 @@ | ||||
|                         </a> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <a title="Notifications" wire:navigate | ||||
|                         <a title="Notifications" | ||||
|                             class="{{ request()->is('notifications*') ? 'menu-item-active menu-item' : 'menu-item' }}" | ||||
|                             href="{{ route('notifications.email') }}"> | ||||
|                             <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> | ||||
| @@ -234,7 +234,7 @@ | ||||
|                         </a> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <a title="Keys & Tokens" wire:navigate | ||||
|                         <a title="Keys & Tokens" | ||||
|                             class="{{ request()->is('security*') ? 'menu-item-active menu-item' : 'menu-item' }}" | ||||
|                             href="{{ route('security.private-key.index') }}"> | ||||
|                             <svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> | ||||
| @@ -246,7 +246,7 @@ | ||||
|                         </a> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <a title="Tags" wire:navigate | ||||
|                         <a title="Tags" | ||||
|                             class="{{ request()->is('tags*') ? 'menu-item-active menu-item' : 'menu-item' }}" | ||||
|                             href="{{ route('tags.show') }}"> | ||||
|                             <svg class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> | ||||
| @@ -275,7 +275,7 @@ | ||||
|                         </a> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <a title="Profile" wire:navigate | ||||
|                         <a title="Profile" | ||||
|                             class="{{ request()->is('profile*') ? 'menu-item-active menu-item' : 'menu-item' }}" | ||||
|                             href="{{ route('profile') }}"> | ||||
|                             <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" | ||||
| @@ -290,7 +290,7 @@ | ||||
|                         </a> | ||||
|                     </li> | ||||
|                     <li> | ||||
|                         <a title="Teams" wire:navigate | ||||
|                         <a title="Teams" | ||||
|                             class="{{ request()->is('team*') ? 'menu-item-active menu-item' : 'menu-item' }}" | ||||
|                             href="{{ route('team.index') }}"> | ||||
|                             <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" | ||||
| @@ -309,7 +309,7 @@ | ||||
|                     </li> | ||||
|                     @if (isCloud()) | ||||
|                         <li> | ||||
|                             <a title="Subscription" wire:navigate | ||||
|                             <a title="Subscription" | ||||
|                                 class="{{ request()->is('subscription*') ? 'menu-item-active menu-item' : 'menu-item' }}" | ||||
|                                 href="{{ route('subscription.show') }}"> | ||||
|                                 <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"> | ||||
| @@ -324,7 +324,7 @@ | ||||
|                     @if (isInstanceAdmin()) | ||||
|                         <li> | ||||
| 
 | ||||
|                             <a title="Settings" wire:navigate | ||||
|                             <a title="Settings" | ||||
|                                 class="{{ request()->is('settings*') ? 'menu-item-active menu-item' : 'menu-item' }}" | ||||
|                                 href="/settings"> | ||||
|                                 <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" | ||||
| @@ -343,7 +343,7 @@ | ||||
|                     @if (isCloud() || isDev()) | ||||
|                         @if (isInstanceAdmin() || session('impersonating')) | ||||
|                             <li> | ||||
|                                 <a wire:navigate title="Admin" class="menu-item" href="/admin"> | ||||
|                                 <a title="Admin" class="menu-item" href="/admin"> | ||||
|                                     <svg class="text-pink-600 icon" viewBox="0 0 256 256" | ||||
|                                         xmlns="http://www.w3.org/2000/svg"> | ||||
|                                         <path fill="currentColor" | ||||
| @@ -363,7 +363,7 @@ | ||||
|                         @endpersist | ||||
|                     @endif | ||||
|                     <li> | ||||
|                         <a title="Onboarding" wire:navigate | ||||
|                         <a title="Onboarding" | ||||
|                             class="{{ request()->is('onboarding*') ? 'menu-item-active menu-item' : 'menu-item' }}" | ||||
|                             href="{{ route('onboarding') }}"> | ||||
|                             <svg class="icon" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"> | ||||
|   | ||||
| @@ -4,27 +4,22 @@ | ||||
|     <div class="navbar-main"> | ||||
|         <nav class="flex items-center gap-6 min-h-10"> | ||||
|             <a class="{{ request()->routeIs('notifications.email') ? 'dark:text-white' : '' }}" | ||||
|                 wire:navigate | ||||
|                 href="{{ route('notifications.email') }}"> | ||||
|                 <button>Email</button> | ||||
|             </a> | ||||
|             <a class="{{ request()->routeIs('notifications.discord') ? 'dark:text-white' : '' }}" | ||||
|                 wire:navigate | ||||
|                 href="{{ route('notifications.discord') }}"> | ||||
|                 <button>Discord</button> | ||||
|             </a> | ||||
|              <a class="{{ request()->routeIs('notifications.telegram') ? 'dark:text-white' : '' }}" | ||||
|                 wire:navigate | ||||
|             <a class="{{ request()->routeIs('notifications.telegram') ? 'dark:text-white' : '' }}" | ||||
|                 href="{{ route('notifications.telegram') }}"> | ||||
|                 <button>Telegram</button> | ||||
|             </a> | ||||
|             <a class="{{ request()->routeIs('notifications.slack') ? 'dark:text-white' : '' }}" | ||||
|                 wire:navigate | ||||
|                 href="{{ route('notifications.slack') }}"> | ||||
|                 <button>Slack</button> | ||||
|             </a> | ||||
|             <a class="{{ request()->routeIs('notifications.pushover') ? 'dark:text-white' : '' }}" | ||||
|                 wire:navigate | ||||
|                 href="{{ route('notifications.pushover') }}"> | ||||
|                 <button>Pushover</button> | ||||
|             </a> | ||||
|   | ||||
| @@ -8,7 +8,6 @@ | ||||
|         <li class="inline-flex items-center"> | ||||
|             <div class="flex items-center"> | ||||
|                 <a class="text-xs truncate lg:text-sm" | ||||
|                     wire:navigate | ||||
|                     href="{{ route('project.show', ['project_uuid' => data_get($resource, 'environment.project.uuid')]) }}"> | ||||
|                     {{ data_get($resource, 'environment.project.name', 'Undefined Name') }}</a> | ||||
|                 <svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor" | ||||
| @@ -22,7 +21,6 @@ | ||||
|         <li> | ||||
|             <div class="flex items-center"> | ||||
|                 <a class="text-xs truncate lg:text-sm" | ||||
|                     wire:navigate | ||||
|                     href="{{ route('project.resource.index', [
 | ||||
|                         'environment_uuid' => data_get($resource, 'environment.uuid'), | ||||
|                         'project_uuid' => data_get($resource, 'environment.project.uuid'), | ||||
|   | ||||
| @@ -3,10 +3,10 @@ | ||||
|     <div class="subtitle">Security related settings.</div> | ||||
|     <div class="navbar-main"> | ||||
|         <nav class="flex items-center gap-6 scrollbar min-h-10"> | ||||
|             <a wire:navigate href="{{ route('security.private-key.index') }}"> | ||||
|             <a href="{{ route('security.private-key.index') }}"> | ||||
|                 <button>Private Keys</button> | ||||
|             </a> | ||||
|             <a wire:navigate href="{{ route('security.api-tokens') }}"> | ||||
|             <a href="{{ route('security.api-tokens') }}"> | ||||
|                 <button>API tokens</button> | ||||
|             </a> | ||||
|         </nav> | ||||
|   | ||||
| @@ -18,7 +18,7 @@ | ||||
|     <div class="subtitle">{{ data_get($server, 'name') }}</div> | ||||
|     <div class="navbar-main"> | ||||
|         <nav class="flex items-center gap-6 overflow-x-scroll sm:overflow-x-hidden scrollbar min-h-10 whitespace-nowrap"> | ||||
|             <a wire:navigate class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}" | ||||
|             <a class="{{ request()->routeIs('server.show') ? 'dark:text-white' : '' }}" | ||||
|                 href="{{ route('server.show', [
 | ||||
|                     'server_uuid' => data_get($server, 'uuid'), | ||||
|                 ]) }}">
 | ||||
| @@ -26,14 +26,14 @@ | ||||
|             </a> | ||||
| 
 | ||||
|             @if (!$server->isSwarmWorker() && !$server->settings->is_build_server) | ||||
|                 <a wire:navigate class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }}" | ||||
|                 <a class="{{ request()->routeIs('server.proxy') ? 'dark:text-white' : '' }}" | ||||
|                     href="{{ route('server.proxy', [
 | ||||
|                         'server_uuid' => data_get($server, 'uuid'), | ||||
|                     ]) }}">
 | ||||
|                     <button>Proxy</button> | ||||
|                 </a> | ||||
|             @endif | ||||
|             <a wire:navigate class="{{ request()->routeIs('server.resources') ? 'dark:text-white' : '' }}" | ||||
|             <a class="{{ request()->routeIs('server.resources') ? 'dark:text-white' : '' }}" | ||||
|                 href="{{ route('server.resources', [
 | ||||
|                     'server_uuid' => data_get($server, 'uuid'), | ||||
|                 ]) }}">
 | ||||
|   | ||||
| @@ -1,14 +1,14 @@ | ||||
| @if ($server->proxySet()) | ||||
|     <div class="flex flex-col items-start gap-2 min-w-fit"> | ||||
|         <a wire:navigate class="{{ request()->routeIs('server.proxy') ? 'menu-item menu-item-active' : 'menu-item' }}" | ||||
|         <a class="{{ request()->routeIs('server.proxy') ? 'menu-item menu-item-active' : 'menu-item' }}" | ||||
|             href="{{ route('server.proxy', $parameters) }}"> | ||||
|             <button>Configuration</button> | ||||
|         </a> | ||||
|         <a wire:navigate class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'menu-item menu-item-active' : 'menu-item' }}" | ||||
|         <a class="{{ request()->routeIs('server.proxy.dynamic-confs') ? 'menu-item menu-item-active' : 'menu-item' }}" | ||||
|             href="{{ route('server.proxy.dynamic-confs', $parameters) }}"> | ||||
|             <button>Dynamic Configurations</button> | ||||
|         </a> | ||||
|         <a wire:navigate class="{{ request()->routeIs('server.proxy.logs') ? 'menu-item menu-item-active' : 'menu-item' }}" | ||||
|         <a class="{{ request()->routeIs('server.proxy.logs') ? 'menu-item menu-item-active' : 'menu-item' }}" | ||||
|             href="{{ route('server.proxy.logs', $parameters) }}"> | ||||
|             <button>Logs</button> | ||||
|         </a> | ||||
|   | ||||
| @@ -1,34 +1,34 @@ | ||||
| <div class="flex flex-col items-start gap-2 min-w-fit"> | ||||
|     <a wire:navigate class="menu-item {{ $activeMenu === 'general' ? 'menu-item-active' : '' }}" | ||||
|     <a class="menu-item {{ $activeMenu === 'general' ? 'menu-item-active' : '' }}" | ||||
|         href="{{ route('server.show', ['server_uuid' => $server->uuid]) }}">General</a> | ||||
|     @if ($server->isFunctional()) | ||||
|         <a wire:navigate class="menu-item {{ $activeMenu === 'advanced' ? 'menu-item-active' : '' }}" | ||||
|         <a class="menu-item {{ $activeMenu === 'advanced' ? 'menu-item-active' : '' }}" | ||||
|             href="{{ route('server.advanced', ['server_uuid' => $server->uuid]) }}">Advanced | ||||
|         </a> | ||||
|     @endif | ||||
|     <a wire:navigate class="menu-item {{ $activeMenu === 'private-key' ? 'menu-item-active' : '' }}" | ||||
|     <a class="menu-item {{ $activeMenu === 'private-key' ? 'menu-item-active' : '' }}" | ||||
|         href="{{ route('server.private-key', ['server_uuid' => $server->uuid]) }}">Private Key | ||||
|     </a> | ||||
|     @if (!$server->isLocalhost()) | ||||
|         <a wire:navigate class="menu-item {{ $activeMenu === 'cloudflare-tunnels' ? 'menu-item-active' : '' }}" | ||||
|         <a class="menu-item {{ $activeMenu === 'cloudflare-tunnels' ? 'menu-item-active' : '' }}" | ||||
|             href="{{ route('server.cloudflare-tunnels', ['server_uuid' => $server->uuid]) }}">Cloudflare | ||||
|             Tunnels</a> | ||||
|     @endif | ||||
|     @if ($server->isFunctional()) | ||||
|         <a wire:navigate class="menu-item {{ $activeMenu === 'docker-cleanup' ? 'menu-item-active' : '' }}" | ||||
|         <a class="menu-item {{ $activeMenu === 'docker-cleanup' ? 'menu-item-active' : '' }}" | ||||
|             href="{{ route('server.docker-cleanup', ['server_uuid' => $server->uuid]) }}">Docker Cleanup | ||||
|         </a> | ||||
|         <a wire:navigate class="menu-item {{ $activeMenu === 'destinations' ? 'menu-item-active' : '' }}" | ||||
|         <a class="menu-item {{ $activeMenu === 'destinations' ? 'menu-item-active' : '' }}" | ||||
|             href="{{ route('server.destinations', ['server_uuid' => $server->uuid]) }}">Destinations | ||||
|         </a> | ||||
|         <a wire:navigate class="menu-item {{ $activeMenu === 'log-drains' ? 'menu-item-active' : '' }}" | ||||
|         <a class="menu-item {{ $activeMenu === 'log-drains' ? 'menu-item-active' : '' }}" | ||||
|             href="{{ route('server.log-drains', ['server_uuid' => $server->uuid]) }}">Log | ||||
|             Drains</a> | ||||
|         <a class="menu-item {{ $activeMenu === 'metrics' ? 'menu-item-active' : '' }}" | ||||
|             href="{{ route('server.charts', ['server_uuid' => $server->uuid]) }}">Metrics</a> | ||||
|     @endif | ||||
|     @if (!$server->isLocalhost()) | ||||
|         <a wire:navigate class="menu-item {{ $activeMenu === 'danger' ? 'menu-item-active' : '' }}" | ||||
|         <a class="menu-item {{ $activeMenu === 'danger' ? 'menu-item-active' : '' }}" | ||||
|             href="{{ route('server.delete', ['server_uuid' => $server->uuid]) }}">Danger</a> | ||||
|     @endif | ||||
| </div> | ||||
|   | ||||
| @@ -4,20 +4,18 @@ | ||||
|     <div class="navbar-main"> | ||||
|         <nav class="flex items-center gap-6 min-h-10 whitespace-nowrap"> | ||||
|             <a class="{{ request()->routeIs('settings.index') ? 'dark:text-white' : '' }}" | ||||
|                 wire:navigate | ||||
|                 href="{{ route('settings.index') }}"> | ||||
|                 <button>Configuration</button> | ||||
|             </a> | ||||
|             <a class="{{ request()->routeIs('settings.backup') ? 'dark:text-white' : '' }}" | ||||
|                 wire:navigate | ||||
|                 href="{{ route('settings.backup') }}"> | ||||
|                 <button>Backup</button> | ||||
|             </a> | ||||
|             <a class="{{ request()->routeIs('settings.email') ? 'dark:text-white' : '' }}" wire:navigate | ||||
|             <a class="{{ request()->routeIs('settings.email') ? 'dark:text-white' : '' }}" | ||||
|                 href="{{ route('settings.email') }}"> | ||||
|                 <button>Transactional Email</button> | ||||
|             </a> | ||||
|             <a class="{{ request()->routeIs('settings.oauth') ? 'dark:text-white' : '' }}" wire:navigate | ||||
|             <a class="{{ request()->routeIs('settings.oauth') ? 'dark:text-white' : '' }}" | ||||
|                 href="{{ route('settings.oauth') }}"> | ||||
|                 <button>OAuth</button> | ||||
|             </a> | ||||
|   | ||||
| @@ -8,16 +8,15 @@ | ||||
|     <div class="subtitle">Team wide configurations.</div> | ||||
|     <div class="navbar-main"> | ||||
|         <nav class="flex items-center gap-6 min-h-10"> | ||||
|             <a class="{{ request()->routeIs('team.index') ? 'dark:text-white' : '' }}" wire:navigate | ||||
|                 href="{{ route('team.index') }}"> | ||||
|             <a class="{{ request()->routeIs('team.index') ? 'dark:text-white' : '' }}" href="{{ route('team.index') }}"> | ||||
|                 <button>General</button> | ||||
|             </a> | ||||
|             <a class="{{ request()->routeIs('team.member.index') ? 'dark:text-white' : '' }}" wire:navigate | ||||
|             <a class="{{ request()->routeIs('team.member.index') ? 'dark:text-white' : '' }}" | ||||
|                 href="{{ route('team.member.index') }}"> | ||||
|                 <button>Members</button> | ||||
|             </a> | ||||
|             @if (isInstanceAdmin()) | ||||
|                 <a class="{{ request()->routeIs('team.admin-view') ? 'dark:text-white' : '' }}" wire:navigate | ||||
|                 <a class="{{ request()->routeIs('team.admin-view') ? 'dark:text-white' : '' }}" | ||||
|                     href="{{ route('team.admin-view') }}"> | ||||
|                     <button>Admin View</button> | ||||
|                 </a> | ||||
|   | ||||
| @@ -83,6 +83,9 @@ | ||||
| 
 | ||||
|             function checkTheme() { | ||||
|                 theme = localStorage.theme | ||||
|                 if (theme == 'system') { | ||||
|                     theme = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light' | ||||
|                 } | ||||
|                 if (theme == 'dark') { | ||||
|                     baseColor = '#FCD452' | ||||
|                     textColor = '#ffffff' | ||||
|   | ||||
| @@ -30,7 +30,7 @@ | ||||
|                             </div> | ||||
|                             <div class="flex items-center justify-center gap-2 text-xs font-bold"> | ||||
|                                 @if ($project->environments->first()) | ||||
|                                     <a class="hover:underline" wire:navigate wire:click.stop | ||||
|                                     <a class="hover:underline" wire:click.stop | ||||
|                                         href="{{ route('project.resource.create', [
 | ||||
|                                             'project_uuid' => $project->uuid, | ||||
|                                             'environment_uuid' => $project->environments->first()->uuid, | ||||
| @@ -38,7 +38,7 @@ | ||||
|                                         <span class="p-2 font-bold">+ Add Resource</span> | ||||
|                                     </a> | ||||
|                                 @endif | ||||
|                                 <a class="hover:underline" wire:navigate wire:click.stop | ||||
|                                 <a class="hover:underline" wire:click.stop | ||||
|                                     href="{{ route('project.edit', ['project_uuid' => $project->uuid]) }}"> | ||||
|                                     Settings | ||||
|                                 </a> | ||||
| @@ -54,8 +54,7 @@ | ||||
|                     <x-modal-input buttonTitle="Add" title="New Project"> | ||||
|                         <livewire:project.add-empty /> | ||||
|                     </x-modal-input> your first project or | ||||
|                     go to the <a class="underline dark:text-white" wire:navigate | ||||
|                         href="{{ route('onboarding') }}">onboarding</a> page. | ||||
|                     go to the <a class="underline dark:text-white" href="{{ route('onboarding') }}">onboarding</a> page. | ||||
|                 </div> | ||||
|             </div> | ||||
|         @endif | ||||
| @@ -66,7 +65,7 @@ | ||||
|         @if ($servers->count() > 0) | ||||
|             <div class="grid grid-cols-1 gap-2 xl:grid-cols-2"> | ||||
|                 @foreach ($servers as $server) | ||||
|                     <a wire:navigate href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}" | ||||
|                     <a href="{{ route('server.show', ['server_uuid' => data_get($server, 'uuid')]) }}" | ||||
|                         @class([ | ||||
|                             'gap-2 border cursor-pointer box group', | ||||
|                             'border-transparent' => $server->settings->is_reachable, | ||||
| @@ -103,8 +102,7 @@ | ||||
|                             <livewire:security.private-key.create from="server" /> | ||||
|                         </x-modal-input> a private key | ||||
|                         or | ||||
|                         go to the <a class="underline dark:text-white" wire:navigate | ||||
|                             href="{{ route('onboarding') }}">onboarding</a> | ||||
|                         go to the <a class="underline dark:text-white" href="{{ route('onboarding') }}">onboarding</a> | ||||
|                         page. | ||||
|                     </div> | ||||
|                 </div> | ||||
| @@ -116,8 +114,7 @@ | ||||
|                             <livewire:server.create /> | ||||
|                         </x-modal-input> your first server | ||||
|                         or | ||||
|                         go to the <a class="underline dark:text-white" wire:navigate | ||||
|                             href="{{ route('onboarding') }}">onboarding</a> | ||||
|                         go to the <a class="underline dark:text-white" href="{{ route('onboarding') }}">onboarding</a> | ||||
|                         page. | ||||
|                     </div> | ||||
|                 </div> | ||||
| @@ -142,12 +139,11 @@ | ||||
|                     <h4 class="pb-2">{{ $serverName }}</h4> | ||||
|                     <div class="grid grid-cols-1 gap-2 lg:grid-cols-3"> | ||||
|                         @foreach ($deployments as $deployment) | ||||
|                             <a wire:navigate href="{{ data_get($deployment, 'deployment_url') }}" | ||||
|                                 @class([ | ||||
|                                     'gap-2 cursor-pointer box group border-l-2 border-dotted', | ||||
|                                     'dark:border-coolgray-300' => data_get($deployment, 'status') === 'queued', | ||||
|                                     'border-yellow-500' => data_get($deployment, 'status') === 'in_progress', | ||||
|                                 ])> | ||||
|                             <a href="{{ data_get($deployment, 'deployment_url') }}" @class([ | ||||
|                                 'gap-2 cursor-pointer box group border-l-2 border-dotted', | ||||
|                                 'dark:border-coolgray-300' => data_get($deployment, 'status') === 'queued', | ||||
|                                 'border-yellow-500' => data_get($deployment, 'status') === 'in_progress', | ||||
|                             ])> | ||||
|                                 <div class="flex flex-col justify-center mx-6"> | ||||
|                                     <div class="box-title"> | ||||
|                                         {{ data_get($deployment, 'application_name') }} | ||||
|   | ||||
| @@ -15,7 +15,7 @@ | ||||
|         @forelse ($servers as $server) | ||||
|             @forelse ($server->destinations() as $destination) | ||||
|                 @if ($destination->getMorphClass() === 'App\Models\StandaloneDocker') | ||||
|                     <a class="box group" wire:navigate | ||||
|                     <a class="box group" | ||||
|                         href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}"> | ||||
|                         <div class="flex flex-col mx-6"> | ||||
|                             <div class="box-title">{{ $destination->name }}</div> | ||||
| @@ -24,7 +24,7 @@ | ||||
|                     </a> | ||||
|                 @endif | ||||
|                 @if ($destination->getMorphClass() === 'App\Models\SwarmDocker') | ||||
|                     <a class="box group" wire:navigate | ||||
|                     <a class="box group" | ||||
|                         href="{{ route('destination.show', ['destination_uuid' => data_get($destination, 'uuid')]) }}"> | ||||
|                         <div class="flex flex-col mx-6"> | ||||
|                             <div class="box-title">{{ $destination->name }}</div> | ||||
|   | ||||
| @@ -9,30 +9,27 @@ | ||||
|     <div class="flex flex-col h-full gap-8 pt-6 sm:flex-row"> | ||||
|         <div class="flex flex-col items-start gap-2 min-w-fit"> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.application.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                 wire:navigate>General</a> | ||||
|                 href="{{ route('project.application.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">General</a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.application.advanced', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                 wire:navigate>Advanced</a> | ||||
|                 href="{{ route('project.application.advanced', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Advanced</a> | ||||
|             @if ($application->destination->server->isSwarm()) | ||||
|                 <a class="menu-item" wire:current.exact="menu-item-active" | ||||
|                     href="{{ route('project.application.swarm', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                     wire:navigate>Swarm Configuration</a> | ||||
|                     href="{{ route('project.application.swarm', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Swarm | ||||
|                     Configuration</a> | ||||
|             @endif | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.application.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                 wire:navigate>Environment Variables</a> | ||||
|                 href="{{ route('project.application.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Environment | ||||
|                 Variables</a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.application.persistent-storage', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                 wire:navigate>Persistent Storage</a> | ||||
|                 href="{{ route('project.application.persistent-storage', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Persistent | ||||
|                 Storage</a> | ||||
|             @if ($application->git_based()) | ||||
|                 <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                     href="{{ route('project.application.source', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                     wire:navigate>Git Source</a> | ||||
|                     href="{{ route('project.application.source', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Git | ||||
|                     Source</a> | ||||
|             @endif | ||||
|             <a class="menu-item flex items-center gap-2" wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.application.servers', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                 wire:navigate>Servers | ||||
|                 href="{{ route('project.application.servers', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Servers | ||||
|                 @if (str($application->status)->contains('degraded')) | ||||
|                     <span title="Some servers are unavailable"> | ||||
|                         <svg class="w-4 h-4 text-error" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"> | ||||
| @@ -51,36 +48,32 @@ | ||||
|                 @endif | ||||
|             </a> | ||||
|             <a class="menu-item" wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.application.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                 wire:navigate>Scheduled Tasks</a> | ||||
|                 href="{{ route('project.application.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Scheduled | ||||
|                 Tasks</a> | ||||
|             <a class="menu-item" wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.application.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                 wire:navigate>Webhooks</a> | ||||
|                 href="{{ route('project.application.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Webhooks</a> | ||||
|             <a class="menu-item" wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.application.preview-deployments', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                 wire:navigate>Preview Deployments</a> | ||||
|                 href="{{ route('project.application.preview-deployments', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Preview | ||||
|                 Deployments</a> | ||||
|             @if ($application->build_pack !== 'dockercompose') | ||||
|                 <a class="menu-item" wire:current.exact="menu-item-active" | ||||
|                     href="{{ route('project.application.healthcheck', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                     wire:navigate>Healthcheck</a> | ||||
|                     href="{{ route('project.application.healthcheck', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Healthcheck</a> | ||||
|             @endif | ||||
|             <a class="menu-item" wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.application.rollback', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                 wire:navigate>Rollback</a> | ||||
|                 href="{{ route('project.application.rollback', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Rollback</a> | ||||
|             <a class="menu-item" wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.application.resource-limits', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                 wire:navigate>Resource Limits</a> | ||||
|                 href="{{ route('project.application.resource-limits', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Resource | ||||
|                 Limits</a> | ||||
|             <a class="menu-item" wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.application.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                 wire:navigate>Resource Operations</a> | ||||
|                 href="{{ route('project.application.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Resource | ||||
|                 Operations</a> | ||||
|             <a class="menu-item" wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.application.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Metrics</a> | ||||
|             <a class="menu-item" wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.application.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                 wire:navigate>Tags</a> | ||||
|                 href="{{ route('project.application.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Tags</a> | ||||
|             <a class="menu-item" wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.application.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}" | ||||
|                 wire:navigate>Danger Zone</a> | ||||
|                 href="{{ route('project.application.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]) }}">Danger | ||||
|                 Zone</a> | ||||
|         </div> | ||||
|         <div class="w-full"> | ||||
|             @if ($currentRoute === 'project.application.configuration') | ||||
|   | ||||
| @@ -3,18 +3,21 @@ | ||||
|     <h1>Deployments</h1> | ||||
|     <livewire:project.shared.configuration-checker :resource="$application" /> | ||||
|     <livewire:project.application.heading :application="$application" /> | ||||
|     <div class="flex flex-col gap-2 pb-10" @if (!$skip) wire:poll.5000ms='reload_deployments' @endif> | ||||
|     <div class="flex flex-col gap-2 pb-10" | ||||
|         @if (!$skip) wire:poll.5000ms='reload_deployments' @endif> | ||||
|         <div class="flex items-end gap-2 pt-4"> | ||||
|             <h2>Deployments <span class="text-xs">({{ $deployments_count }})</span></h2> | ||||
|             @if ($deployments_count > 0) | ||||
|                 <x-forms.button disabled="{{ !$show_prev }}" wire:click="previous_page('{{ $default_take }}')"> | ||||
|                     <svg class="w-6 h-6" viewBox="0 0 24 24"> | ||||
|                         <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m14 6l-6 6l6 6z" /> | ||||
|                         <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" | ||||
|                             stroke-width="2" d="m14 6l-6 6l6 6z" /> | ||||
|                     </svg> | ||||
|                 </x-forms.button> | ||||
|                 <x-forms.button disabled="{{ !$show_next }}" wire:click="next_page('{{ $default_take }}')"> | ||||
|                     <svg class="w-6 h-6" viewBox="0 0 24 24"> | ||||
|                         <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m10 18l6-6l-6-6z" /> | ||||
|                         <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" | ||||
|                             stroke-width="2" d="m10 18l6-6l-6-6z" /> | ||||
|                     </svg> | ||||
|                 </x-forms.button> | ||||
|             @endif | ||||
| @@ -28,44 +31,57 @@ | ||||
|         @forelse ($deployments as $deployment) | ||||
|             <div @class([ | ||||
|                 'p-2 border-l-2 bg-white dark:bg-coolgray-100', | ||||
|                 'border-blue-500/50 border-dashed' => data_get($deployment, 'status') === 'in_progress', | ||||
|                 'border-purple-500/50 border-dashed' => data_get($deployment, 'status') === 'queued', | ||||
|                 'border-white border-dashed' => data_get($deployment, 'status') === 'cancelled-by-user', | ||||
|                 'border-blue-500/50 border-dashed' => | ||||
|                     data_get($deployment, 'status') === 'in_progress', | ||||
|                 'border-purple-500/50 border-dashed' => | ||||
|                     data_get($deployment, 'status') === 'queued', | ||||
|                 'border-white border-dashed' => | ||||
|                     data_get($deployment, 'status') === 'cancelled-by-user', | ||||
|                 'border-error' => data_get($deployment, 'status') === 'failed', | ||||
|                 'border-success' => data_get($deployment, 'status') === 'finished', | ||||
|             ])> | ||||
|                 <a href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}" wire:navigate class="block"> | ||||
|                 <a href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}" class="block"> | ||||
|                     <div class="flex flex-col"> | ||||
|                         <div class="flex items-center gap-2 mb-2"> | ||||
|                             <span @class([ | ||||
|                                 'px-3 py-1 rounded-md text-xs font-medium shadow-sm', | ||||
|                                 'bg-blue-100/80 text-blue-700 dark:bg-blue-500/20 dark:text-blue-300' => data_get($deployment, 'status') === 'in_progress', | ||||
|                                 'bg-purple-100/80 text-purple-700 dark:bg-purple-500/20 dark:text-purple-300' => data_get($deployment, 'status') === 'queued', | ||||
|                                 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-200' => data_get($deployment, 'status') === 'failed', | ||||
|                                 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-200' => data_get($deployment, 'status') === 'finished', | ||||
|                                 'bg-gray-100 text-gray-700 dark:bg-gray-600/30 dark:text-gray-300' => data_get($deployment, 'status') === 'cancelled-by-user', | ||||
|                                 'bg-blue-100/80 text-blue-700 dark:bg-blue-500/20 dark:text-blue-300' => | ||||
|                                     data_get($deployment, 'status') === 'in_progress', | ||||
|                                 'bg-purple-100/80 text-purple-700 dark:bg-purple-500/20 dark:text-purple-300' => | ||||
|                                     data_get($deployment, 'status') === 'queued', | ||||
|                                 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-200' => | ||||
|                                     data_get($deployment, 'status') === 'failed', | ||||
|                                 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-200' => | ||||
|                                     data_get($deployment, 'status') === 'finished', | ||||
|                                 'bg-gray-100 text-gray-700 dark:bg-gray-600/30 dark:text-gray-300' => | ||||
|                                     data_get($deployment, 'status') === 'cancelled-by-user', | ||||
|                             ])> | ||||
|                                 @php | ||||
|                                     $statusText = match(data_get($deployment, 'status')) { | ||||
|                                     $statusText = match (data_get($deployment, 'status')) { | ||||
|                                         'finished' => 'Success', | ||||
|                                         'in_progress' => 'In Progress', | ||||
|                                         'cancelled-by-user' => 'Cancelled', | ||||
|                                         'queued' => 'Queued', | ||||
|                                         default => ucfirst(data_get($deployment, 'status')) | ||||
|                                         default => ucfirst(data_get($deployment, 'status')), | ||||
|                                     }; | ||||
|                                 @endphp | ||||
|                                 {{ $statusText }} | ||||
|                             </span> | ||||
|                         </div> | ||||
|                         @if(data_get($deployment, 'status') !== 'queued') | ||||
|                         @if (data_get($deployment, 'status') !== 'queued') | ||||
|                             <div class="text-gray-600 dark:text-gray-400 text-sm"> | ||||
|                                 Started: {{ formatDateInServerTimezone(data_get($deployment, 'created_at'), data_get($application, 'destination.server')) }} | ||||
|                                 @if($deployment->status !== 'in_progress' && $deployment->status !== 'cancelled-by-user') | ||||
|                                     <br>Ended: {{ formatDateInServerTimezone(data_get($deployment, 'finished_at'), data_get($application, 'destination.server')) }} | ||||
|                                     <br>Duration: {{ calculateDuration(data_get($deployment, 'created_at'), data_get($deployment, 'finished_at')) }} | ||||
|                                     <br>Finished {{ \Carbon\Carbon::parse(data_get($deployment, 'finished_at'))->diffForHumans() }} | ||||
|                                 Started: | ||||
|                                 {{ formatDateInServerTimezone(data_get($deployment, 'created_at'), data_get($application, 'destination.server')) }} | ||||
|                                 @if ($deployment->status !== 'in_progress' && $deployment->status !== 'cancelled-by-user') | ||||
|                                     <br>Ended: | ||||
|                                     {{ formatDateInServerTimezone(data_get($deployment, 'finished_at'), data_get($application, 'destination.server')) }} | ||||
|                                     <br>Duration: | ||||
|                                     {{ calculateDuration(data_get($deployment, 'created_at'), data_get($deployment, 'finished_at')) }} | ||||
|                                     <br>Finished | ||||
|                                     {{ \Carbon\Carbon::parse(data_get($deployment, 'finished_at'))->diffForHumans() }} | ||||
|                                 @elseif($deployment->status === 'in_progress') | ||||
|                                     <br>Running for: {{ calculateDuration(data_get($deployment, 'created_at'), now()) }} | ||||
|                                     <br>Running for: | ||||
|                                     {{ calculateDuration(data_get($deployment, 'created_at'), now()) }} | ||||
|                                 @endif | ||||
|                             </div> | ||||
|                         @endif | ||||
| @@ -75,14 +91,13 @@ | ||||
|                                 <div x-data="{ expanded: false }"> | ||||
|                                     <div class="flex items-center gap-2"> | ||||
|                                         <span class="font-medium">Commit:</span> | ||||
|                                         <a wire:navigate.prevent | ||||
|                                            href="{{ $application->gitCommitLink(data_get($deployment, 'commit')) }}" | ||||
|                                            target="_blank" | ||||
|                                            class="underline"> | ||||
|                                         <a href="{{ $application->gitCommitLink(data_get($deployment, 'commit')) }}" | ||||
|                                             target="_blank" class="underline"> | ||||
|                                             {{ substr(data_get($deployment, 'commit'), 0, 7) }} | ||||
|                                         </a> | ||||
|                                         @if (!$deployment->commitMessage()) | ||||
|                                             <span class="bg-gray-200/70 dark:bg-gray-600/20 px-2 py-0.5 rounded-md text-xs text-gray-800 dark:text-gray-100 border border-gray-400/30"> | ||||
|                                             <span | ||||
|                                                 class="bg-gray-200/70 dark:bg-gray-600/20 px-2 py-0.5 rounded-md text-xs text-gray-800 dark:text-gray-100 border border-gray-400/30"> | ||||
|                                                 @if (data_get($deployment, 'is_webhook')) | ||||
|                                                     Webhook | ||||
|                                                     @if (data_get($deployment, 'pull_request_id')) | ||||
| @@ -101,21 +116,24 @@ | ||||
|                                         @endif | ||||
|                                         @if ($deployment->commitMessage()) | ||||
|                                             <span class="text-gray-600 dark:text-gray-400">-</span> | ||||
|                                             <a wire:navigate.prevent | ||||
|                                                href="{{ $application->gitCommitLink(data_get($deployment, 'commit')) }}" | ||||
|                                                target="_blank" | ||||
|                                                class="text-gray-600 dark:text-gray-400 truncate max-w-md underline"> | ||||
|                                             <a href="{{ $application->gitCommitLink(data_get($deployment, 'commit')) }}" | ||||
|                                                 target="_blank" | ||||
|                                                 class="text-gray-600 dark:text-gray-400 truncate max-w-md underline"> | ||||
|                                                 {{ Str::before($deployment->commitMessage(), "\n") }} | ||||
|                                             </a> | ||||
|                                             @if ($deployment->commitMessage() !== Str::before($deployment->commitMessage(), "\n")) | ||||
|                                                 <button @click="expanded = !expanded" | ||||
|                                                     class="text-gray-600 dark:text-gray-400 flex items-center gap-1"> | ||||
|                                                     <svg x-bind:class="{'rotate-180': expanded}" class="w-4 h-4 transition-transform" viewBox="0 0 24 24"> | ||||
|                                                         <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="m6 9l6 6l6-6"/> | ||||
|                                                     <svg x-bind:class="{ 'rotate-180': expanded }" | ||||
|                                                         class="w-4 h-4 transition-transform" viewBox="0 0 24 24"> | ||||
|                                                         <path fill="none" stroke="currentColor" | ||||
|                                                             stroke-linecap="round" stroke-linejoin="round" | ||||
|                                                             stroke-width="2" d="m6 9l6 6l6-6" /> | ||||
|                                                     </svg> | ||||
|                                                 </button> | ||||
|                                             @endif | ||||
|                                             <span class="bg-gray-200/70 dark:bg-gray-600/20 px-2 py-0.5 rounded-md text-xs text-gray-800 dark:text-gray-100 border border-gray-400/30"> | ||||
|                                             <span | ||||
|                                                 class="bg-gray-200/70 dark:bg-gray-600/20 px-2 py-0.5 rounded-md text-xs text-gray-800 dark:text-gray-100 border border-gray-400/30"> | ||||
|                                                 @if (data_get($deployment, 'is_webhook')) | ||||
|                                                     Webhook | ||||
|                                                     @if (data_get($deployment, 'pull_request_id')) | ||||
| @@ -134,11 +152,10 @@ | ||||
|                                         @endif | ||||
|                                     </div> | ||||
|                                     @if ($deployment->commitMessage()) | ||||
|                                         <div x-show="expanded" | ||||
|                                              x-transition:enter="transition ease-out duration-200" | ||||
|                                              x-transition:enter-start="opacity-0 transform -translate-y-2" | ||||
|                                              x-transition:enter-end="opacity-100 transform translate-y-0" | ||||
|                                              class="mt-2 ml-4 text-gray-600 dark:text-gray-400"> | ||||
|                                         <div x-show="expanded" x-transition:enter="transition ease-out duration-200" | ||||
|                                             x-transition:enter-start="opacity-0 transform -translate-y-2" | ||||
|                                             x-transition:enter-end="opacity-100 transform translate-y-0" | ||||
|                                             class="mt-2 ml-4 text-gray-600 dark:text-gray-400"> | ||||
|                                             {{ Str::after($deployment->commitMessage(), "\n") }} | ||||
|                                         </div> | ||||
|                                     @endif | ||||
|   | ||||
| @@ -3,20 +3,20 @@ | ||||
|     <div class="navbar-main"> | ||||
|         <nav class="flex flex-shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10"> | ||||
|             <a class="{{ request()->routeIs('project.application.configuration') ? 'dark:text-white' : '' }}" | ||||
|                 wire:navigate href="{{ route('project.application.configuration', $parameters) }}"> | ||||
|                 href="{{ route('project.application.configuration', $parameters) }}"> | ||||
|                 Configuration | ||||
|             </a> | ||||
|             <a class="{{ request()->routeIs('project.application.deployment.index') ? 'dark:text-white' : '' }}" | ||||
|                 wire:navigate href="{{ route('project.application.deployment.index', $parameters) }}"> | ||||
|                 href="{{ route('project.application.deployment.index', $parameters) }}"> | ||||
|                 <button>Deployments</button> | ||||
|             </a> | ||||
|             <a class="{{ request()->routeIs('project.application.logs') ? 'dark:text-white' : '' }}" | ||||
|                 wire:navigate href="{{ route('project.application.logs', $parameters) }}"> | ||||
|                 href="{{ route('project.application.logs', $parameters) }}"> | ||||
|                 <button>Logs</button> | ||||
|             </a> | ||||
|             @if (!$application->destination->server->isSwarm()) | ||||
|                 <a class="{{ request()->routeIs('project.application.command') ? 'dark:text-white' : '' }}" | ||||
|                  href="{{ route('project.application.command', $parameters) }}"> | ||||
|                     href="{{ route('project.application.command', $parameters) }}"> | ||||
|                     <button>Terminal</button> | ||||
|                 </a> | ||||
|             @endif | ||||
| @@ -117,7 +117,8 @@ | ||||
|     @script | ||||
|         <script> | ||||
|             $wire.$on('stopEvent', () => { | ||||
|                 $wire.$dispatch('info', 'Stopping application.'); | ||||
|                 $wire.$dispatch('info', | ||||
|                     'Gracefully stopping application, it could take a while depending on the application.'); | ||||
|                 $wire.$call('stop'); | ||||
|             }); | ||||
|         </script> | ||||
|   | ||||
| @@ -8,37 +8,33 @@ | ||||
|     <div class="flex flex-col h-full gap-8 pt-6 sm:flex-row"> | ||||
|         <div class="flex flex-col items-start gap-2 min-w-fit"> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.database.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}" | ||||
|                 wire:navigate>General</a> | ||||
|                 href="{{ route('project.database.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">General</a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.database.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}" | ||||
|                 wire:navigate>Environment Variables</a> | ||||
|                 href="{{ route('project.database.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Environment | ||||
|                 Variables</a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.database.servers', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}" | ||||
|                 wire:navigate>Servers</a> | ||||
|                 href="{{ route('project.database.servers', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Servers</a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.database.persistent-storage', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}" | ||||
|                 wire:navigate>Persistent Storage</a> | ||||
|                 href="{{ route('project.database.persistent-storage', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Persistent | ||||
|                 Storage</a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.database.import-backups', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Import | ||||
|                 Backups</a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.database.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}" | ||||
|                 wire:navigate>Webhooks</a> | ||||
|                 href="{{ route('project.database.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Webhooks</a> | ||||
|             <a class="menu-item" wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.database.resource-limits', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}" | ||||
|                 wire:navigate>Resource Limits</a> | ||||
|                 href="{{ route('project.database.resource-limits', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Resource | ||||
|                 Limits</a> | ||||
|             <a class="menu-item" wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.database.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}" | ||||
|                 wire:navigate>Resource Operations</a> | ||||
|                 href="{{ route('project.database.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Resource | ||||
|                 Operations</a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.database.metrics', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Metrics</a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.database.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}" | ||||
|                 wire:navigate>Tags</a> | ||||
|                 href="{{ route('project.database.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Tags</a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.database.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}" | ||||
|                 wire:navigate>Danger Zone</a> | ||||
|                 href="{{ route('project.database.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'database_uuid' => $database->uuid]) }}">Danger | ||||
|                 Zone</a> | ||||
|         </div> | ||||
|         <div class="w-full"> | ||||
|             @if ($currentRoute === 'project.database.configuration') | ||||
|   | ||||
| @@ -9,12 +9,12 @@ | ||||
|     <div class="navbar-main"> | ||||
|         <nav | ||||
|             class="flex overflow-x-scroll flex-shrink-0 gap-6 items-center whitespace-nowrap sm:overflow-x-hidden scrollbar min-h-10"> | ||||
|             <a wire:navigate class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}" | ||||
|             <a class="{{ request()->routeIs('project.database.configuration') ? 'dark:text-white' : '' }}" | ||||
|                 href="{{ route('project.database.configuration', $parameters) }}"> | ||||
|                 <button>Configuration</button> | ||||
|             </a> | ||||
| 
 | ||||
|             <a wire:navigate class="{{ request()->routeIs('project.database.logs') ? 'dark:text-white' : '' }}" | ||||
|             <a class="{{ request()->routeIs('project.database.logs') ? 'dark:text-white' : '' }}" | ||||
|                 href="{{ route('project.database.logs', $parameters) }}"> | ||||
|                 <button>Logs</button> | ||||
|             </a> | ||||
| @@ -27,8 +27,7 @@ | ||||
|                     $database->getMorphClass() === 'App\Models\StandaloneMongodb' || | ||||
|                     $database->getMorphClass() === 'App\Models\StandaloneMysql' || | ||||
|                     $database->getMorphClass() === 'App\Models\StandaloneMariadb') | ||||
|                 <a wire:navigate | ||||
|                     class="{{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}" | ||||
|                 <a class="{{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}" | ||||
|                     href="{{ route('project.database.backup.index', $parameters) }}"> | ||||
|                     <button>Backups</button> | ||||
|                 </a> | ||||
|   | ||||
| @@ -1,34 +1,52 @@ | ||||
| <div> | ||||
|     <div class="flex flex-col gap-2"> | ||||
|         @forelse($database->scheduledBackups as $backup) | ||||
|             @if ($type == 'database') | ||||
|                 <a class="box" | ||||
|                     wire:navigate | ||||
|                     href="{{ route('project.database.backup.execution', [...$parameters, 'backup_uuid' => $backup->uuid]) }}"> | ||||
|                     <div class="flex flex-col"> | ||||
|                         <div>Frequency: {{ $backup->frequency }} | ||||
|                             ({{ data_get($backup->server(), 'settings.server_timezone', 'Instance timezone') }}) | ||||
|                         </div> | ||||
|                         <div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div> | ||||
|         @if ($database->is_migrated && blank($database->custom_type)) | ||||
|             <div> | ||||
|                 <div>Select the type of | ||||
|                     database to enable automated backups.</div> | ||||
|                 <div class="pb-4"> If your database is not listed, automated backups are not supported.</div> | ||||
|                 <form wire:submit="setCustomType" class="flex gap-2 items-end"> | ||||
|                     <div class="w-96"> | ||||
|                         <x-forms.select label="Type" id="custom_type"> | ||||
|                             <option selected value="mysql">MySQL</option> | ||||
|                             <option value="mariadb">MariaDB</option> | ||||
|                             <option value="postgresql">PostgreSQL</option> | ||||
|                             <option value="mongodb">MongoDB</option> | ||||
|                         </x-forms.select> | ||||
|                     </div> | ||||
|                 </a> | ||||
|             @else | ||||
|                 <div class="box" wire:navigate wire:click="setSelectedBackup('{{ data_get($backup, 'id') }}')"> | ||||
|                     <div @class([ | ||||
|                         'border-coollabs' => | ||||
|                             data_get($backup, 'id') === data_get($selectedBackup, 'id'), | ||||
|                         'flex flex-col border-l-2 border-transparent', | ||||
|                     ])> | ||||
|                         <div>Frequency: {{ $backup->frequency }} | ||||
|                             ({{ data_get($backup->server(), 'settings.server_timezone', 'Instance timezone') }}) | ||||
|                     <x-forms.button type="submit">Set</x-forms.button> | ||||
|                 </form> | ||||
|             </div> | ||||
|         @else | ||||
|             @forelse($database->scheduledBackups as $backup) | ||||
|                 @if ($type == 'database') | ||||
|                     <a class="box" | ||||
|                         href="{{ route('project.database.backup.execution', [...$parameters, 'backup_uuid' => $backup->uuid]) }}"> | ||||
|                         <div class="flex flex-col"> | ||||
|                             <div>Frequency: {{ $backup->frequency }} | ||||
|                                 ({{ data_get($backup->server(), 'settings.server_timezone', 'Instance timezone') }}) | ||||
|                             </div> | ||||
|                             <div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div> | ||||
|                         </div> | ||||
|                     </a> | ||||
|                 @else | ||||
|                     <div class="box" wire:click="setSelectedBackup('{{ data_get($backup, 'id') }}')"> | ||||
|                         <div @class([ | ||||
|                             'border-coollabs' => | ||||
|                                 data_get($backup, 'id') === data_get($selectedBackup, 'id'), | ||||
|                             'flex flex-col border-l-2 border-transparent', | ||||
|                         ])> | ||||
|                             <div>Frequency: {{ $backup->frequency }} | ||||
|                                 ({{ data_get($backup->server(), 'settings.server_timezone', 'Instance timezone') }}) | ||||
|                             </div> | ||||
|                             <div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div> | ||||
|                         </div> | ||||
|                         <div>Last backup: {{ data_get($backup->latest_log, 'status', 'No backup yet') }}</div> | ||||
|                     </div> | ||||
|                 </div> | ||||
|             @endif | ||||
|         @empty | ||||
|             <div>No scheduled backups configured.</div> | ||||
|         @endforelse | ||||
|                 @endif | ||||
|             @empty | ||||
|                 <div>No scheduled backups configured.</div> | ||||
|             @endforelse | ||||
|         @endif | ||||
|     </div> | ||||
|     @if ($type === 'service-database' && $selectedBackup) | ||||
|         <div class="pt-10"> | ||||
|   | ||||
| @@ -12,7 +12,7 @@ | ||||
|             <ol class="flex flex-wrap items-center gap-y-1"> | ||||
|                 <li class="inline-flex items-center"> | ||||
|                     <div class="flex items-center"> | ||||
|                         <a class="text-xs truncate lg:text-sm" wire:navigate | ||||
|                         <a class="text-xs truncate lg:text-sm" | ||||
|                             href="{{ route('project.show', ['project_uuid' => $project->uuid]) }}"> | ||||
|                             {{ $project->name }}</a> | ||||
|                         <svg aria-hidden="true" class="w-4 h-4 mx-1 font-bold dark:text-warning" fill="currentColor" | ||||
| @@ -25,7 +25,7 @@ | ||||
|                 </li> | ||||
|                 <li> | ||||
|                     <div class="flex items-center"> | ||||
|                         <a class="text-xs truncate lg:text-sm" wire:navigate | ||||
|                         <a class="text-xs truncate lg:text-sm" | ||||
|                             href="{{ route('project.resource.index', ['environment_uuid' => $environment->uuid, 'project_uuid' => $project->uuid]) }}"> | ||||
|                             {{ $environment->name }} | ||||
|                         </a> | ||||
|   | ||||
| @@ -24,11 +24,10 @@ | ||||
|                             <div x-text="project.description"></div> | ||||
|                         </div> | ||||
|                     </div> | ||||
|                     <div class="flex items-center justify-center gap-2 pt-4 pb-2 mr-4 text-xs lg:py-0 lg:justify-normal"> | ||||
|                         <a class="mx-4 font-bold hover:underline"  | ||||
|                            wire:navigate  | ||||
|                            wire:click.stop | ||||
|                            :href="`/project/${project.uuid}/edit`"> | ||||
|                     <div | ||||
|                         class="flex items-center justify-center gap-2 pt-4 pb-2 mr-4 text-xs lg:py-0 lg:justify-normal"> | ||||
|                         <a class="mx-4 font-bold hover:underline" wire:click.stop | ||||
|                             :href="`/project/${project.uuid}/edit`"> | ||||
|                             Settings | ||||
|                         </a> | ||||
|                     </div> | ||||
|   | ||||
| @@ -103,7 +103,7 @@ | ||||
|                                                 :src='service.logo' | ||||
|                                                 x-on:error.window="$event.target.src = service.logo_github_url" | ||||
|                                                 onerror="this.onerror=null; this.src=this.getAttribute('data-fallback');" | ||||
|                                                 x-on:error="$event.target.src = 'coolify-logo.svg'" | ||||
|                                                 x-on:error="$event.target.src = '/coolify-logo.svg'" | ||||
|                                                 :data-fallback='service.logo_github_url' /> | ||||
|                                         </template> | ||||
|                                     </x-slot:logo> | ||||
|   | ||||
| @@ -6,15 +6,15 @@ | ||||
|         <div class="flex items-center gap-2"> | ||||
|             <h1>Resources</h1> | ||||
|             @if ($environment->isEmpty()) | ||||
|                 <a class="button" wire:navigate | ||||
|                 <a class="button" | ||||
|                     href="{{ route('project.clone-me', ['project_uuid' => data_get($project, 'uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}"> | ||||
|                     Clone | ||||
|                 </a> | ||||
|             @else | ||||
|                 <a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}" | ||||
|                     wire:navigate class="button">+ | ||||
|                     class="button">+ | ||||
|                     New</a> | ||||
|                 <a class="button" wire:navigate | ||||
|                 <a class="button" | ||||
|                     href="{{ route('project.clone-me', ['project_uuid' => data_get($project, 'uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}"> | ||||
|                     Clone | ||||
|                 </a> | ||||
| @@ -24,7 +24,7 @@ | ||||
|         <nav class="flex pt-2 pb-6"> | ||||
|             <ol class="flex items-center"> | ||||
|                 <li class="inline-flex items-center"> | ||||
|                     <a class="text-xs truncate lg:text-sm" wire:navigate | ||||
|                     <a class="text-xs truncate lg:text-sm" | ||||
|                         href="{{ route('project.show', ['project_uuid' => data_get($parameters, 'project_uuid')]) }}"> | ||||
|                         {{ $project->name }}</a> | ||||
|                 </li> | ||||
| @@ -44,7 +44,7 @@ | ||||
|         </nav> | ||||
|     </div> | ||||
|     @if ($environment->isEmpty()) | ||||
|         <a wire:navigate href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}" | ||||
|         <a href="{{ route('project.resource.create', ['project_uuid' => data_get($parameters, 'project_uuid'), 'environment_uuid' => data_get($environment, 'uuid')]) }}" | ||||
|             class="items-center justify-center box">+ Add New Resource</a> | ||||
|     @else | ||||
|         <div x-data="searchComponent()"> | ||||
| @@ -61,7 +61,7 @@ | ||||
|                 class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3"> | ||||
|                 <template x-for="item in filteredApplications" :key="item.uuid"> | ||||
|                     <span> | ||||
|                         <a class="h-24 box group" wire:navigate :href="item.hrefLink"> | ||||
|                         <a class="h-24 box group" :href="item.hrefLink"> | ||||
|                             <div class="flex flex-col w-full"> | ||||
|                                 <div class="flex gap-2 px-4"> | ||||
|                                     <div class="pb-2 truncate box-title" x-text="item.name"></div> | ||||
| @@ -90,15 +90,10 @@ | ||||
|                         <div | ||||
|                             class="flex flex-wrap gap-1 pt-1 group-hover:dark:text-white group-hover:text-black group min-h-6"> | ||||
|                             <template x-for="tag in item.tags"> | ||||
|                                 <a :href="`/tags/${tag.name}`"  | ||||
|                                    wire:navigate  | ||||
|                                    class="tag"  | ||||
|                                    x-text="tag.name"> | ||||
|                                 <a :href="`/tags/${tag.name}`" class="tag" x-text="tag.name"> | ||||
|                                 </a> | ||||
|                             </template> | ||||
|                             <a :href="`${item.hrefLink}/tags`"  | ||||
|                                wire:navigate  | ||||
|                                class="add-tag"> | ||||
|                             <a :href="`${item.hrefLink}/tags`" class="add-tag"> | ||||
|                                 Add tag | ||||
|                             </a> | ||||
|                         </div> | ||||
| @@ -112,7 +107,7 @@ | ||||
|                 class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3"> | ||||
|                 <template x-for="item in filteredDatabases" :key="item.uuid"> | ||||
|                     <span> | ||||
|                         <a class="h-24 box group" wire:navigate :href="item.hrefLink"> | ||||
|                         <a class="h-24 box group" :href="item.hrefLink"> | ||||
|                             <div class="flex flex-col w-full"> | ||||
|                                 <div class="flex gap-2 px-4"> | ||||
|                                     <div class="pb-2 truncate box-title" x-text="item.name"></div> | ||||
| @@ -141,15 +136,10 @@ | ||||
|                         <div | ||||
|                             class="flex flex-wrap gap-1 pt-1 group-hover:dark:text-white group-hover:text-black group min-h-6"> | ||||
|                             <template x-for="tag in item.tags"> | ||||
|                                 <a :href="`/tags/${tag.name}`"  | ||||
|                                    wire:navigate  | ||||
|                                    class="tag"  | ||||
|                                    x-text="tag.name"> | ||||
|                                 <a :href="`/tags/${tag.name}`" class="tag" x-text="tag.name"> | ||||
|                                 </a> | ||||
|                             </template> | ||||
|                             <a :href="`${item.hrefLink}/tags`"  | ||||
|                                wire:navigate  | ||||
|                                class="add-tag"> | ||||
|                             <a :href="`${item.hrefLink}/tags`" class="add-tag"> | ||||
|                                 Add tag | ||||
|                             </a> | ||||
|                         </div> | ||||
| @@ -163,7 +153,7 @@ | ||||
|                 class="grid grid-cols-1 gap-4 pt-4 lg:grid-cols-2 xl:grid-cols-3"> | ||||
|                 <template x-for="item in filteredServices" :key="item.uuid"> | ||||
|                     <span> | ||||
|                         <a class="h-24 box group" wire:navigate :href="item.hrefLink"> | ||||
|                         <a class="h-24 box group" :href="item.hrefLink"> | ||||
|                             <div class="flex flex-col w-full"> | ||||
|                                 <div class="flex gap-2 px-4"> | ||||
|                                     <div class="pb-2 truncate box-title" x-text="item.name"></div> | ||||
| @@ -192,15 +182,10 @@ | ||||
|                         <div | ||||
|                             class="flex flex-wrap gap-1 pt-1 group-hover:dark:text-white group-hover:text-black group min-h-6"> | ||||
|                             <template x-for="tag in item.tags"> | ||||
|                                 <a :href="`/tags/${tag.name}`"  | ||||
|                                    wire:navigate  | ||||
|                                    class="tag"  | ||||
|                                    x-text="tag.name"> | ||||
|                                 <a :href="`/tags/${tag.name}`" class="tag" x-text="tag.name"> | ||||
|                                 </a> | ||||
|                             </template> | ||||
|                             <a :href="`${item.hrefLink}/tags`"  | ||||
|                                wire:navigate  | ||||
|                                class="add-tag"> | ||||
|                             <a :href="`${item.hrefLink}/tags`" class="add-tag"> | ||||
|                                 Add tag | ||||
|                             </a> | ||||
|                         </div> | ||||
|   | ||||
| @@ -9,31 +9,28 @@ | ||||
|             <a class="menu-item sm:min-w-fit" target="_blank" href="{{ $service->documentation() }}">Documentation | ||||
|                 <x-external-link /></a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.service.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}" | ||||
|                 wire:navigate>General</a> | ||||
|                 href="{{ route('project.service.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">General</a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.service.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}" | ||||
|                 wire:navigate>Environment Variables</a> | ||||
|                 href="{{ route('project.service.environment-variables', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Environment | ||||
|                 Variables</a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.service.storages', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}" | ||||
|                 wire:navigate>Persistent Storages</a> | ||||
|                 href="{{ route('project.service.storages', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Persistent | ||||
|                 Storages</a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.service.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}" | ||||
|                 wire:navigate>Scheduled Tasks</a> | ||||
|                 href="{{ route('project.service.scheduled-tasks.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Scheduled | ||||
|                 Tasks</a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.service.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}" | ||||
|                 wire:navigate>Webhooks</a> | ||||
|                 href="{{ route('project.service.webhooks', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Webhooks</a> | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.service.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}" | ||||
|                 wire:navigate>Resource Operations</a> | ||||
|                 href="{{ route('project.service.resource-operations', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Resource | ||||
|                 Operations</a> | ||||
| 
 | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.service.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}" | ||||
|                 wire:navigate>Tags</a> | ||||
|                 href="{{ route('project.service.tags', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Tags</a> | ||||
| 
 | ||||
|             <a class='menu-item' wire:current.exact="menu-item-active" | ||||
|                 href="{{ route('project.service.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}" | ||||
|                 wire:navigate>Danger Zone</a> | ||||
|                 href="{{ route('project.service.danger', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">Danger | ||||
|                 Zone</a> | ||||
|         </div> | ||||
|         <div class="w-full"> | ||||
|             @if ($currentRoute === 'project.service.configuration') | ||||
| @@ -42,7 +39,7 @@ | ||||
|                 <div class="grid grid-cols-1 gap-2 pt-4 xl:grid-cols-1" wire:poll.10000ms="check_status"> | ||||
|                     @foreach ($applications as $application) | ||||
|                         <div @class([ | ||||
|                             'border-l border-dashed border-red-500 ' => str( | ||||
|                             'border-l border-dashed border-red-500' => str( | ||||
|                                 $application->status)->contains(['exited']), | ||||
|                             'border-l border-dashed border-success' => str( | ||||
|                                 $application->status)->contains(['running']), | ||||
| @@ -95,8 +92,7 @@ | ||||
|                                 </div> | ||||
|                                 <div class="flex items-center px-4"> | ||||
|                                     <a class="mx-4 text-xs font-bold hover:underline" | ||||
|                                         href="{{ route('project.service.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid, 'stack_service_uuid' => $application->uuid]) }}" | ||||
|                                         wire:navigate> | ||||
|                                         href="{{ route('project.service.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid, 'stack_service_uuid' => $application->uuid]) }}"> | ||||
|                                         Settings | ||||
|                                     </a> | ||||
|                                     @if (str($application->status)->contains('running')) | ||||
| @@ -142,16 +138,14 @@ | ||||
|                                     <div class="text-xs">{{ $database->status }}</div> | ||||
|                                 </div> | ||||
|                                 <div class="flex items-center px-4"> | ||||
|                                     @if ($database->isBackupSolutionAvailable()) | ||||
|                                     @if ($database->isBackupSolutionAvailable() || $database->is_migrated) | ||||
|                                         <a class="mx-4 text-xs font-bold hover:underline" | ||||
|                                             href="{{ route('project.service.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid, 'stack_service_uuid' => $database->uuid]) }}#backups" | ||||
|                                             wire:navigate> | ||||
|                                             href="{{ route('project.service.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid, 'stack_service_uuid' => $database->uuid]) }}#backups"> | ||||
|                                             Backups | ||||
|                                         </a> | ||||
|                                     @endif | ||||
|                                     <a class="mx-4 text-xs font-bold hover:underline" | ||||
|                                         href="{{ route('project.service.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid, 'stack_service_uuid' => $database->uuid]) }}" | ||||
|                                         wire:navigate> | ||||
|                                         href="{{ route('project.service.index', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid, 'stack_service_uuid' => $database->uuid]) }}"> | ||||
|                                         Settings | ||||
|                                     </a> | ||||
|                                     @if (str($database->status)->contains('running')) | ||||
| @@ -177,8 +171,8 @@ | ||||
|                 <div class="pb-4">Persistent storage to preserve data between deployments.</div> | ||||
|                 <div class="pb-4 dark:text-warning text-coollabs">If you would like to add a volume, you must add it to | ||||
|                     your compose file (<a class="underline" | ||||
|                         href="{{ route('project.service.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}" | ||||
|                         wire:navigate>General tab</a>).</div> | ||||
|                         href="{{ route('project.service.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'service_uuid' => $service->uuid]) }}">General | ||||
|                         tab</a>).</div> | ||||
|                 @foreach ($applications as $application) | ||||
|                     <livewire:project.service.storage wire:key="application-{{ $application->id }}" | ||||
|                         :resource="$application" /> | ||||
|   | ||||
| @@ -7,6 +7,11 @@ | ||||
|                 <h2>{{ Str::headline($database->name) }}</h2> | ||||
|             @endif | ||||
|             <x-forms.button type="submit">Save</x-forms.button> | ||||
|             <x-modal-confirmation wire:click="convertToApplication" title="Convert to Application" | ||||
|                 buttonTitle="Convert to Application" submitAction="convertToApplication" :actions="['The selected resource will be converted to an application.']" | ||||
|                 confirmationText="{{ Str::headline($database->name) }}" | ||||
|                 confirmationLabel="Please confirm the execution of the actions by entering the Service Database Name below" | ||||
|                 shortConfirmationLabel="Service Database Name" step3ButtonText="Permanently Delete" /> | ||||
|             <x-modal-confirmation title="Confirm Service Database Deletion?" buttonTitle="Delete" isErrorButton | ||||
|                 submitAction="delete" :actions="['The selected service database container will be stopped and permanently deleted.']" confirmationText="{{ Str::headline($database->name) }}" | ||||
|                 confirmationLabel="Please confirm the execution of the actions by entering the Service Database Name below" | ||||
| @@ -18,7 +23,7 @@ | ||||
|                 <x-forms.input label="Description" id="database.description"></x-forms.input> | ||||
|                 <x-forms.input required | ||||
|                     helper="You can change the image you would like to deploy.<br><br><span class='dark:text-warning'>WARNING. You could corrupt your data. Only do it if you know what you are doing.</span>" | ||||
|                     label="Image Tag" id="database.image"></x-forms.input> | ||||
|                     label="Image" id="database.image"></x-forms.input> | ||||
|             </div> | ||||
|             <div class="flex items-end gap-2"> | ||||
|                 <x-forms.input placeholder="5432" disabled="{{ $database->is_public }}" id="database.public_port" | ||||
|   | ||||
| @@ -3,17 +3,16 @@ | ||||
|     <div class="flex flex-col h-full gap-8 pt-6 sm:flex-row"> | ||||
|         <div class="flex flex-col items-start gap-2 min-w-fit"> | ||||
|             <a class="menu-item" | ||||
|                 class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-active' : '' }}" wire:navigate | ||||
|                 class="{{ request()->routeIs('project.service.configuration') ? 'menu-item-active' : '' }}" | ||||
|                 href="{{ route('project.service.configuration', [...$parameters, 'stack_service_uuid' => null]) }}"> | ||||
|                 <button><- Back</button> | ||||
|             </a> | ||||
|             <a class="menu-item" :class="activeTab === 'general' && 'menu-item-active'" | ||||
|                 @click.prevent="activeTab = 'general'; window.location.hash = 'general'; if(window.location.search) window.location.search = ''" | ||||
|                 wire:navigate href="#">General</a> | ||||
|             @if ($serviceDatabase?->isBackupSolutionAvailable()) | ||||
|                 href="#">General</a> | ||||
|             @if ($serviceDatabase?->isBackupSolutionAvailable() || $serviceDatabase?->is_migrated) | ||||
|                 <a :class="activeTab === 'backups' && 'menu-item-active'" class="menu-item" | ||||
|                     @click.prevent="activeTab = 'backups'; window.location.hash = 'backups'" wire:navigate | ||||
|                     href="#backups">Backups</a> | ||||
|                     @click.prevent="activeTab = 'backups'; window.location.hash = 'backups'" href="#backups">Backups</a> | ||||
|             @endif | ||||
|         </div> | ||||
|         <div class="w-full"> | ||||
| @@ -34,18 +33,20 @@ | ||||
|                 <div x-cloak x-show="activeTab === 'general'" class="h-full"> | ||||
|                     <livewire:project.service.database :database="$serviceDatabase" /> | ||||
|                 </div> | ||||
|                 @if ($serviceDatabase->isBackupSolutionAvailable()) | ||||
|                 @if ($serviceDatabase?->isBackupSolutionAvailable() || $serviceDatabase?->is_migrated) | ||||
|                     <div x-cloak x-show="activeTab === 'backups'"> | ||||
|                         <div class="flex gap-2 "> | ||||
|                             <h2 class="pb-4">Scheduled Backups</h2> | ||||
|                             <x-modal-input buttonTitle="+ Add" title="New Scheduled Backup"> | ||||
|                                 <livewire:project.database.create-scheduled-backup :database="$serviceDatabase" /> | ||||
|                             </x-modal-input> | ||||
|                             @if (filled($serviceDatabase->custom_type) || !$serviceDatabase->is_migrated) | ||||
|                                 <x-modal-input buttonTitle="+ Add" title="New Scheduled Backup"> | ||||
|                                     <livewire:project.database.create-scheduled-backup :database="$serviceDatabase" /> | ||||
|                                 </x-modal-input> | ||||
|                             @endif | ||||
|                         </div> | ||||
|                         <livewire:project.database.scheduled-backups :database="$serviceDatabase" /> | ||||
|                     </div> | ||||
|                 @endif | ||||
|             </div> | ||||
|         @endisset | ||||
|             @endisset | ||||
|         </div> | ||||
|     </div> | ||||
| </div> | ||||
| </div> | ||||
|   | ||||
| @@ -10,11 +10,11 @@ | ||||
|     <x-resources.breadcrumbs :resource="$service" :parameters="$parameters" /> | ||||
|     <div class="navbar-main" x-data">
 | ||||
|         <nav class="flex flex-shrink-0 gap-6 items-center whitespace-nowrap scrollbar min-h-10"> | ||||
|             <a wire:navigate class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}" | ||||
|             <a class="{{ request()->routeIs('project.service.configuration') ? 'dark:text-white' : '' }}" | ||||
|                 href="{{ route('project.service.configuration', $parameters) }}"> | ||||
|                 <button>Configuration</button> | ||||
|             </a> | ||||
|             <a wire:navigate class="{{ request()->routeIs('project.service.logs') ? 'dark:text-white' : '' }}" | ||||
|             <a class="{{ request()->routeIs('project.service.logs') ? 'dark:text-white' : '' }}" | ||||
|                 href="{{ route('project.service.logs', $parameters) }}"> | ||||
|                 <button>Logs</button> | ||||
|             </a> | ||||
| @@ -129,7 +129,7 @@ | ||||
|             <div class="flex flex-wrap order-first gap-2 items-center sm:order-last"> | ||||
|                 <div class="text-error"> | ||||
|                     Unable to deploy. <a class="underline font-bold cursor-pointer" | ||||
|                         href="{{ route('project.service.environment-variables', $parameters) }}" wire:navigate> | ||||
|                         href="{{ route('project.service.environment-variables', $parameters) }}"> | ||||
|                         Required environment variables missing.</a> | ||||
|                 </div> | ||||
|             </div> | ||||
| @@ -138,7 +138,7 @@ | ||||
|     @script | ||||
|         <script> | ||||
|             $wire.$on('stopEvent', () => { | ||||
|                 $wire.$dispatch('info', 'Stopping service.'); | ||||
|                 $wire.$dispatch('info', 'Gracefully stopping service, it could take a while depending on the service.'); | ||||
|                 $wire.$call('stop'); | ||||
|             }); | ||||
|             $wire.$on('startEvent', async () => { | ||||
|   | ||||
| @@ -7,11 +7,15 @@ | ||||
|                 <h2>{{ Str::headline($application->name) }}</h2> | ||||
|             @endif | ||||
|             <x-forms.button type="submit">Save</x-forms.button> | ||||
|             <x-modal-confirmation title="Confirm Service Application Deletion?" buttonTitle="Delete" isErrorButton | ||||
|                 submitAction="delete" {{-- :checkboxes="$checkboxes"  --}} :actions="['The selected service application container will be stopped and permanently deleted.']" | ||||
|             <x-modal-confirmation wire:click="convertToDatabase" title="Convert to Database" | ||||
|                 buttonTitle="Convert to Database" submitAction="convertToDatabase" :actions="['The selected resource will be converted to a service database.']" | ||||
|                 confirmationText="{{ Str::headline($application->name) }}" | ||||
|                 confirmationLabel="Please confirm the execution of the actions by entering the Service Application Name below" | ||||
|                 shortConfirmationLabel="Service Application Name" step3ButtonText="Permanently Delete" /> | ||||
|             <x-modal-confirmation title="Confirm Service Application Deletion?" buttonTitle="Delete" isErrorButton | ||||
|                 submitAction="delete" :actions="['The selected service application container will be stopped and permanently deleted.']" confirmationText="{{ Str::headline($application->name) }}" | ||||
|                 confirmationLabel="Please confirm the execution of the actions by entering the Service Application Name below" | ||||
|                 shortConfirmationLabel="Service Application Name" step3ButtonText="Permanently Delete" /> | ||||
|         </div> | ||||
|         <div class="flex flex-col gap-2"> | ||||
|             <div class="flex gap-2"> | ||||
| @@ -30,7 +34,7 @@ | ||||
|                             helper="You can specify one domain with path or more with comma. You can specify a port to bind the domain to.<br><br><span class='text-helper'>Example</span><br>- http://app.coolify.io,https://cloud.coolify.io/dashboard<br>- http://app.coolify.io/api/v3<br>- http://app.coolify.io:3000 -> app.coolify.io will point to port 3000 inside the container. "></x-forms.input> | ||||
|                     @endif | ||||
|                 @endif | ||||
|                 <x-forms.input required | ||||
|                 <x-forms.input | ||||
|                     helper="You can change the image you would like to deploy.<br><br><span class='dark:text-warning'>WARNING. You could corrupt your data. Only do it if you know what you are doing.</span>" | ||||
|                     label="Image" id="application.image"></x-forms.input> | ||||
|             </div> | ||||
|   | ||||
| @@ -8,7 +8,7 @@ | ||||
|     @elseif(!$resource->destination->server->isMetricsEnabled()) | ||||
|         <div class="alert alert-warning">Metrics are only available for servers with Sentinel & Metrics enabled!</div> | ||||
|         <div> Go to <a class="underline dark:text-white" | ||||
|                 wire:navigate href="{{ route('server.show', $resource->destination->server->uuid) }}">Server settings</a> to | ||||
|                 href="{{ route('server.show', $resource->destination->server->uuid) }}">Server settings</a> to | ||||
|             enable | ||||
|             it.</div> | ||||
|     @else | ||||
|   | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 Andras Bacsai
					Andras Bacsai