Merge branch 'next' into main
This commit is contained in:
		| @@ -1,16 +1,12 @@ | |||||||
| # Coolify Configuration |  | ||||||
| APP_ID= | APP_ID= | ||||||
| APP_NAME=Coolify | APP_NAME=Coolify | ||||||
| APP_KEY= | APP_KEY= | ||||||
|  |  | ||||||
| # PostgreSQL Database Configuration |  | ||||||
| DB_USERNAME=coolify | DB_USERNAME=coolify | ||||||
| DB_PASSWORD= | DB_PASSWORD= | ||||||
|  |  | ||||||
| # Redis Configuration |  | ||||||
| REDIS_PASSWORD= | REDIS_PASSWORD= | ||||||
|  |  | ||||||
| # Pusher Configuration |  | ||||||
| PUSHER_APP_ID= | PUSHER_APP_ID= | ||||||
| PUSHER_APP_KEY= | PUSHER_APP_KEY= | ||||||
| PUSHER_APP_SECRET= | PUSHER_APP_SECRET= | ||||||
|   | |||||||
| @@ -58,7 +58,7 @@ Special thanks to our biggest sponsors! | |||||||
| * [PFGlabs](https://pfglabs.com/?ref=coolify.io) - Build real project with Golang. | * [PFGlabs](https://pfglabs.com/?ref=coolify.io) - Build real project with Golang. | ||||||
| * [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets. | * [Treive](https://trieve.ai/?ref=coolify.io) - An AI-powered search and discovery platform for enhancing information retrieval in large datasets. | ||||||
| * [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers. | * [Blacksmith](https://blacksmith.sh/?ref=coolify.io) - A cloud-native platform for automating infrastructure provisioning and management across multiple cloud providers. | ||||||
| * [Brand Dev](https://brand.dev/?ref=coolify.io) - A web development agency specializing in creating custom digital experiences and brand identities. | * [Brand Dev](https://brand.dev/?ref=coolify.io) - The #1 Brand API for B2B software startups - instantly pull logos, fonts, descriptions, social links, slogans, and so much more from any domain via a single api call. | ||||||
| * [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries. | * [Jobscollider](https://jobscollider.com/remote-jobs?ref=coolify.io) - A job search platform connecting professionals with remote work opportunities across various industries. | ||||||
| * [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools. | * [Hostinger](https://www.hostinger.com/vps/coolify-hosting?ref=coolify.io) - A web hosting provider offering affordable hosting solutions, domain registration, and website building tools. | ||||||
| * [Glueops](https://www.glueops.dev/?ref=coolify.io) - A DevOps consulting company providing infrastructure automation and cloud optimization services. | * [Glueops](https://www.glueops.dev/?ref=coolify.io) - A DevOps consulting company providing infrastructure automation and cloud optimization services. | ||||||
| @@ -75,6 +75,7 @@ Special thanks to our biggest sponsors! | |||||||
| <a href="https://www.runpod.io/?ref=coolify.io"> | <a href="https://www.runpod.io/?ref=coolify.io"> | ||||||
| <svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a> | <svg style="width:60px;height:60px;background:#fff;" xmlns="http://www.w3.org/2000/svg" version="1.0" viewBox="0 0 200 200"><g><path d="M74.5 51.1c-25.4 14.9-27 16-29.6 20.2-1.8 3-1.9 5.3-1.9 32.3 0 21.7.3 29.4 1.3 30.6 1.9 2.5 46.7 27.9 48.5 27.6 1.5-.3 1.7-3.1 2-27.7.2-21.9 0-27.8-1.1-29.5-.8-1.2-9.9-6.8-20.2-12.6-10.3-5.8-19.4-11.5-20.2-12.7-1.8-2.6-.9-5.9 1.8-7.4 1.6-.8 6.3 0 21.8 4C87.8 78.7 98 81 99.6 81c4.4 0 49.9-25.9 49.9-28.4 0-1.6-3.4-2.8-24-8.2-13.2-3.5-25.1-6.3-26.5-6.3-1.4.1-12.4 5.9-24.5 13z"></path><path d="m137.2 68.1-3.3 2.1 6.3 3.7c3.5 2 6.3 4.3 6.3 5.1 0 .9-8 6.1-19.4 12.6-10.6 6-20 11.9-20.7 12.9-1.2 1.6-1.4 7.2-1.2 29.4.3 24.8.5 27.6 2 27.9 1.8.3 46.6-25.1 48.6-27.6.9-1.2 1.2-8.8 1.2-30.2s-.3-29-1.2-30.2c-1.6-1.9-12.1-7.8-13.9-7.8-.8 0-2.9 1-4.7 2.1z"></path></g></svg></a> | ||||||
| <a href="https://lightspeed.run/?ref=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a> | <a href="https://lightspeed.run/?ref=coolify.io"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a> | ||||||
|  | <a href="https://dartnode.com/?ref=coolify.io"><img src="https://github.com/DartNode-com.png" width="60px" alt="DartNode"/></a> | ||||||
| <a href="https://www.flint.sh/en/home?ref=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a> | <a href="https://www.flint.sh/en/home?ref=coolify.io"> <img src="https://github.com/Flint-company.png" width="60px" alt="FlintCompany"/></a> | ||||||
| <a href="https://americancloud.com/?ref=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a> | <a href="https://americancloud.com/?ref=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a> | ||||||
| <a href="https://cryptojobslist.com/?ref=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a> | <a href="https://cryptojobslist.com/?ref=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a> | ||||||
|   | |||||||
| @@ -91,16 +91,9 @@ class RunRemoteProcess | |||||||
|         } else { |         } else { | ||||||
|             if ($processResult->exitCode() == 0) { |             if ($processResult->exitCode() == 0) { | ||||||
|                 $status = ProcessStatus::FINISHED; |                 $status = ProcessStatus::FINISHED; | ||||||
|             } |             } else { | ||||||
|             if ($processResult->exitCode() != 0 && ! $this->ignore_errors) { |  | ||||||
|                 $status = ProcessStatus::ERROR; |                 $status = ProcessStatus::ERROR; | ||||||
|             } |             } | ||||||
|             // if (($processResult->exitCode() == 0 && $this->is_finished) || $this->activity->properties->get('status') === ProcessStatus::FINISHED->value) {
 |  | ||||||
|             //     $status = ProcessStatus::FINISHED;
 |  | ||||||
|             // }
 |  | ||||||
|             // if ($processResult->exitCode() != 0 && !$this->ignore_errors) {
 |  | ||||||
|             //     $status = ProcessStatus::ERROR;
 |  | ||||||
|             // }
 |  | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $this->activity->properties = $this->activity->properties->merge([ |         $this->activity->properties = $this->activity->properties->merge([ | ||||||
| @@ -110,9 +103,6 @@ class RunRemoteProcess | |||||||
|             'status' => $status->value, |             'status' => $status->value, | ||||||
|         ]); |         ]); | ||||||
|         $this->activity->save(); |         $this->activity->save(); | ||||||
|         if ($processResult->exitCode() != 0 && ! $this->ignore_errors) { |  | ||||||
|             throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode()); |  | ||||||
|         } |  | ||||||
|         if ($this->call_event_on_finish) { |         if ($this->call_event_on_finish) { | ||||||
|             try { |             try { | ||||||
|                 if ($this->call_event_data) { |                 if ($this->call_event_data) { | ||||||
| @@ -128,6 +118,9 @@ class RunRemoteProcess | |||||||
|                 Log::error('Error calling event: '.$e->getMessage()); |                 Log::error('Error calling event: '.$e->getMessage()); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         if ($processResult->exitCode() != 0 && ! $this->ignore_errors) { | ||||||
|  |             throw new \RuntimeException($processResult->errorOutput(), $processResult->exitCode()); | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         return $processResult; |         return $processResult; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -49,11 +49,7 @@ class StartClickhouse | |||||||
|                             'hard' => 262144, |                             'hard' => 262144, | ||||||
|                         ], |                         ], | ||||||
|                     ], |                     ], | ||||||
|                     'labels' => [ |                     'labels' => defaultDatabaseLabels($this->database)->toArray(), | ||||||
|                         'coolify.managed' => 'true', |  | ||||||
|                         'coolify.type' => 'database', |  | ||||||
|                         'coolify.databaseId' => $this->database->id, |  | ||||||
|                     ], |  | ||||||
|                     'healthcheck' => [ |                     'healthcheck' => [ | ||||||
|                         'test' => "clickhouse-client --password {$this->database->clickhouse_admin_password} --query 'SELECT 1'", |                         'test' => "clickhouse-client --password {$this->database->clickhouse_admin_password} --query 'SELECT 1'", | ||||||
|                         'interval' => '5s', |                         'interval' => '5s', | ||||||
|   | |||||||
| @@ -67,6 +67,10 @@ class StartDatabaseProxy | |||||||
|                     $type = \App\Models\StandaloneClickhouse::class; |                     $type = \App\Models\StandaloneClickhouse::class; | ||||||
|                     $containerName = "clickhouse-{$database->service->uuid}"; |                     $containerName = "clickhouse-{$database->service->uuid}"; | ||||||
|                     break; |                     break; | ||||||
|  |                 case 'standalone-supabase/postgres': | ||||||
|  |                     $type = \App\Models\StandalonePostgresql::class; | ||||||
|  |                     $containerName = "supabase-db-{$database->service->uuid}"; | ||||||
|  |                     break; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if ($type === \App\Models\StandaloneRedis::class) { |         if ($type === \App\Models\StandaloneRedis::class) { | ||||||
|   | |||||||
| @@ -46,11 +46,7 @@ class StartDragonfly | |||||||
|                     'networks' => [ |                     'networks' => [ | ||||||
|                         $this->database->destination->network, |                         $this->database->destination->network, | ||||||
|                     ], |                     ], | ||||||
|                     'labels' => [ |                     'labels' => defaultDatabaseLabels($this->database)->toArray(), | ||||||
|                         'coolify.managed' => 'true', |  | ||||||
|                         'coolify.type' => 'database', |  | ||||||
|                         'coolify.databaseId' => $this->database->id, |  | ||||||
|                     ], |  | ||||||
|                     'healthcheck' => [ |                     'healthcheck' => [ | ||||||
|                         'test' => "redis-cli -a {$this->database->dragonfly_password} ping", |                         'test' => "redis-cli -a {$this->database->dragonfly_password} ping", | ||||||
|                         'interval' => '5s', |                         'interval' => '5s', | ||||||
|   | |||||||
| @@ -48,11 +48,7 @@ class StartKeydb | |||||||
|                     'networks' => [ |                     'networks' => [ | ||||||
|                         $this->database->destination->network, |                         $this->database->destination->network, | ||||||
|                     ], |                     ], | ||||||
|                     'labels' => [ |                     'labels' => defaultDatabaseLabels($this->database)->toArray(), | ||||||
|                         'coolify.managed' => 'true', |  | ||||||
|                         'coolify.type' => 'database', |  | ||||||
|                         'coolify.databaseId' => $this->database->id, |  | ||||||
|                     ], |  | ||||||
|                     'healthcheck' => [ |                     'healthcheck' => [ | ||||||
|                         'test' => "keydb-cli --pass {$this->database->keydb_password} ping", |                         'test' => "keydb-cli --pass {$this->database->keydb_password} ping", | ||||||
|                         'interval' => '5s', |                         'interval' => '5s', | ||||||
|   | |||||||
| @@ -43,11 +43,7 @@ class StartMariadb | |||||||
|                     'networks' => [ |                     'networks' => [ | ||||||
|                         $this->database->destination->network, |                         $this->database->destination->network, | ||||||
|                     ], |                     ], | ||||||
|                     'labels' => [ |                     'labels' => defaultDatabaseLabels($this->database)->toArray(), | ||||||
|                         'coolify.managed' => 'true', |  | ||||||
|                         'coolify.type' => 'database', |  | ||||||
|                         'coolify.databaseId' => $this->database->id, |  | ||||||
|                     ], |  | ||||||
|                     'healthcheck' => [ |                     'healthcheck' => [ | ||||||
|                         'test' => ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized'], |                         'test' => ['CMD', 'healthcheck.sh', '--connect', '--innodb_initialized'], | ||||||
|                         'interval' => '5s', |                         'interval' => '5s', | ||||||
|   | |||||||
| @@ -51,11 +51,7 @@ class StartMongodb | |||||||
|                     'networks' => [ |                     'networks' => [ | ||||||
|                         $this->database->destination->network, |                         $this->database->destination->network, | ||||||
|                     ], |                     ], | ||||||
|                     'labels' => [ |                     'labels' => defaultDatabaseLabels($this->database)->toArray(), | ||||||
|                         'coolify.managed' => 'true', |  | ||||||
|                         'coolify.type' => 'database', |  | ||||||
|                         'coolify.databaseId' => $this->database->id, |  | ||||||
|                     ], |  | ||||||
|                     'healthcheck' => [ |                     'healthcheck' => [ | ||||||
|                         'test' => [ |                         'test' => [ | ||||||
|                             'CMD', |                             'CMD', | ||||||
|   | |||||||
| @@ -43,11 +43,7 @@ class StartMysql | |||||||
|                     'networks' => [ |                     'networks' => [ | ||||||
|                         $this->database->destination->network, |                         $this->database->destination->network, | ||||||
|                     ], |                     ], | ||||||
|                     'labels' => [ |                     'labels' => defaultDatabaseLabels($this->database)->toArray(), | ||||||
|                         'coolify.managed' => 'true', |  | ||||||
|                         'coolify.type' => 'database', |  | ||||||
|                         'coolify.databaseId' => $this->database->id, |  | ||||||
|                     ], |  | ||||||
|                     'healthcheck' => [ |                     'healthcheck' => [ | ||||||
|                         'test' => ['CMD', 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', "-p{$this->database->mysql_root_password}"], |                         'test' => ['CMD', 'mysqladmin', 'ping', '-h', 'localhost', '-u', 'root', "-p{$this->database->mysql_root_password}"], | ||||||
|                         'interval' => '5s', |                         'interval' => '5s', | ||||||
|   | |||||||
| @@ -23,6 +23,9 @@ class StartPostgresql | |||||||
|         $this->database = $database; |         $this->database = $database; | ||||||
|         $container_name = $this->database->uuid; |         $container_name = $this->database->uuid; | ||||||
|         $this->configuration_dir = database_configuration_dir().'/'.$container_name; |         $this->configuration_dir = database_configuration_dir().'/'.$container_name; | ||||||
|  |         if (isDev()) { | ||||||
|  |             $this->configuration_dir = '/var/lib/docker/volumes/coolify_dev_coolify_data/_data/databases/'.$container_name; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
|         $this->commands = [ |         $this->commands = [ | ||||||
|             "echo 'Starting database.'", |             "echo 'Starting database.'", | ||||||
| @@ -47,11 +50,7 @@ class StartPostgresql | |||||||
|                     'networks' => [ |                     'networks' => [ | ||||||
|                         $this->database->destination->network, |                         $this->database->destination->network, | ||||||
|                     ], |                     ], | ||||||
|                     'labels' => [ |                     'labels' => defaultDatabaseLabels($this->database)->toArray(), | ||||||
|                         'coolify.managed' => 'true', |  | ||||||
|                         'coolify.type' => 'database', |  | ||||||
|                         'coolify.databaseId' => $this->database->id, |  | ||||||
|                     ], |  | ||||||
|                     'healthcheck' => [ |                     'healthcheck' => [ | ||||||
|                         'test' => [ |                         'test' => [ | ||||||
|                             'CMD-SHELL', |                             'CMD-SHELL', | ||||||
| @@ -78,7 +77,7 @@ class StartPostgresql | |||||||
|                 ], |                 ], | ||||||
|             ], |             ], | ||||||
|         ]; |         ]; | ||||||
|         if (! is_null($this->database->limits_cpuset)) { |         if (filled($this->database->limits_cpuset)) { | ||||||
|             data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); |             data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset); | ||||||
|         } |         } | ||||||
|         if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { |         if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) { | ||||||
| @@ -108,7 +107,7 @@ class StartPostgresql | |||||||
|                 ]; |                 ]; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if (! is_null($this->database->postgres_conf) && ! empty($this->database->postgres_conf)) { |         if (filled($this->database->postgres_conf)) { | ||||||
|             $docker_compose['services'][$container_name]['volumes'][] = [ |             $docker_compose['services'][$container_name]['volumes'][] = [ | ||||||
|                 'type' => 'bind', |                 'type' => 'bind', | ||||||
|                 'source' => $this->configuration_dir.'/custom-postgres.conf', |                 'source' => $this->configuration_dir.'/custom-postgres.conf', | ||||||
| @@ -199,9 +198,12 @@ class StartPostgresql | |||||||
| 
 | 
 | ||||||
|     private function generate_init_scripts() |     private function generate_init_scripts() | ||||||
|     { |     { | ||||||
|         if (is_null($this->database->init_scripts) || count($this->database->init_scripts) === 0) { |         $this->commands[] = "rm -rf $this->configuration_dir/docker-entrypoint-initdb.d/*"; | ||||||
|  | 
 | ||||||
|  |         if (blank($this->database->init_scripts) || count($this->database->init_scripts) === 0) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|         foreach ($this->database->init_scripts as $init_script) { |         foreach ($this->database->init_scripts as $init_script) { | ||||||
|             $filename = data_get($init_script, 'filename'); |             $filename = data_get($init_script, 'filename'); | ||||||
|             $content = data_get($init_script, 'content'); |             $content = data_get($init_script, 'content'); | ||||||
| @@ -213,10 +215,15 @@ class StartPostgresql | |||||||
| 
 | 
 | ||||||
|     private function add_custom_conf() |     private function add_custom_conf() | ||||||
|     { |     { | ||||||
|         if (is_null($this->database->postgres_conf) || empty($this->database->postgres_conf)) { |         $filename = 'custom-postgres.conf'; | ||||||
|  |         $config_file_path = "$this->configuration_dir/$filename"; | ||||||
|  | 
 | ||||||
|  |         if (blank($this->database->postgres_conf)) { | ||||||
|  |             $this->commands[] = "rm -f $config_file_path"; | ||||||
|  | 
 | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         $filename = 'custom-postgres.conf'; | 
 | ||||||
|         $content = $this->database->postgres_conf; |         $content = $this->database->postgres_conf; | ||||||
|         if (! str($content)->contains('listen_addresses')) { |         if (! str($content)->contains('listen_addresses')) { | ||||||
|             $content .= "\nlisten_addresses = '*'"; |             $content .= "\nlisten_addresses = '*'"; | ||||||
| @@ -224,6 +231,6 @@ class StartPostgresql | |||||||
|             $this->database->save(); |             $this->database->save(); | ||||||
|         } |         } | ||||||
|         $content_base64 = base64_encode($content); |         $content_base64 = base64_encode($content); | ||||||
|         $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $this->configuration_dir/{$filename} > /dev/null"; |         $this->commands[] = "echo '{$content_base64}' | base64 -d | tee $config_file_path > /dev/null"; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -48,11 +48,7 @@ class StartRedis | |||||||
|                     'networks' => [ |                     'networks' => [ | ||||||
|                         $this->database->destination->network, |                         $this->database->destination->network, | ||||||
|                     ], |                     ], | ||||||
|                     'labels' => [ |                     'labels' => defaultDatabaseLabels($this->database)->toArray(), | ||||||
|                         'coolify.managed' => 'true', |  | ||||||
|                         'coolify.type' => 'database', |  | ||||||
|                         'coolify.databaseId' => $this->database->id, |  | ||||||
|                     ], |  | ||||||
|                     'healthcheck' => [ |                     'healthcheck' => [ | ||||||
|                         'test' => [ |                         'test' => [ | ||||||
|                             'CMD-SHELL', |                             'CMD-SHELL', | ||||||
|   | |||||||
| @@ -112,7 +112,7 @@ class GetContainersStatus | |||||||
|                             $preview->update(['last_online_at' => now()]); |                             $preview->update(['last_online_at' => now()]); | ||||||
|                         } |                         } | ||||||
|                     } else { |                     } else { | ||||||
|                         //Notify user that this container should not be there.
 |                         // Notify user that this container should not be there.
 | ||||||
|                     } |                     } | ||||||
|                 } else { |                 } else { | ||||||
|                     $application = $this->applications->where('id', $applicationId)->first(); |                     $application = $this->applications->where('id', $applicationId)->first(); | ||||||
| @@ -125,7 +125,7 @@ class GetContainersStatus | |||||||
|                             $application->update(['last_online_at' => now()]); |                             $application->update(['last_online_at' => now()]); | ||||||
|                         } |                         } | ||||||
|                     } else { |                     } else { | ||||||
|                         //Notify user that this container should not be there.
 |                         // Notify user that this container should not be there.
 | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|   | |||||||
| @@ -39,6 +39,11 @@ class CleanupStuckedResources extends Command | |||||||
|             $servers = Server::all()->filter(function ($server) { |             $servers = Server::all()->filter(function ($server) { | ||||||
|                 return $server->isFunctional(); |                 return $server->isFunctional(); | ||||||
|             }); |             }); | ||||||
|  |             if (isCloud()) { | ||||||
|  |                 $servers = $servers->filter(function ($server) { | ||||||
|  |                     return data_get($server->team->subscription, 'stripe_invoice_paid', false) === true; | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|             foreach ($servers as $server) { |             foreach ($servers as $server) { | ||||||
|                 CleanupHelperContainersJob::dispatch($server); |                 CleanupHelperContainersJob::dispatch($server); | ||||||
|             } |             } | ||||||
|   | |||||||
| @@ -183,7 +183,7 @@ class Emails extends Command | |||||||
|                         'team_id' => 0, |                         'team_id' => 0, | ||||||
|                     ]); |                     ]); | ||||||
|                 } |                 } | ||||||
|                 //$this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail();
 |                 // $this->mail = (new BackupSuccess($backup->frequency, $db->name))->toMail();
 | ||||||
|                 $this->sendEmail(); |                 $this->sendEmail(); | ||||||
|                 break; |                 break; | ||||||
|                 // case 'invitation-link':
 |                 // case 'invitation-link':
 | ||||||
|   | |||||||
							
								
								
									
										178
									
								
								app/Console/Commands/HorizonManage.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										178
									
								
								app/Console/Commands/HorizonManage.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,178 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Console\Commands; | ||||||
|  | 
 | ||||||
|  | use App\Enums\ApplicationDeploymentStatus; | ||||||
|  | use App\Models\ApplicationDeploymentQueue; | ||||||
|  | use App\Repositories\CustomJobRepository; | ||||||
|  | use Illuminate\Console\Command; | ||||||
|  | use Illuminate\Support\Facades\Artisan; | ||||||
|  | use Laravel\Horizon\Contracts\JobRepository; | ||||||
|  | use Laravel\Horizon\Contracts\MetricsRepository; | ||||||
|  | use Laravel\Horizon\Repositories\RedisJobRepository; | ||||||
|  | 
 | ||||||
|  | use function Laravel\Prompts\multiselect; | ||||||
|  | use function Laravel\Prompts\select; | ||||||
|  | use function Laravel\Prompts\table; | ||||||
|  | use function Laravel\Prompts\text; | ||||||
|  | 
 | ||||||
|  | class HorizonManage extends Command | ||||||
|  | { | ||||||
|  |     protected $signature = 'horizon:manage {--can-i-restart-this-worker} {--job-status=}'; | ||||||
|  | 
 | ||||||
|  |     protected $description = 'Manage horizon'; | ||||||
|  | 
 | ||||||
|  |     public function handle() | ||||||
|  |     { | ||||||
|  |         if ($this->option('can-i-restart-this-worker')) { | ||||||
|  |             return $this->isThereAJobInProgress(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ($this->option('job-status')) { | ||||||
|  |             return $this->getJobStatus($this->option('job-status')); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         $action = select( | ||||||
|  |             label: 'What to do?', | ||||||
|  |             options: [ | ||||||
|  |                 'pending' => 'Pending Jobs', | ||||||
|  |                 'running' => 'Running Jobs', | ||||||
|  |                 'can-i-restart-this-worker' => 'Can I restart this worker?', | ||||||
|  |                 'job-status' => 'Job Status', | ||||||
|  |                 'workers' => 'Workers', | ||||||
|  |                 'failed' => 'Failed Jobs', | ||||||
|  |                 'failed-delete' => 'Failed Jobs - Delete', | ||||||
|  |                 'purge-queues' => 'Purge Queues', | ||||||
|  |             ] | ||||||
|  |         ); | ||||||
|  | 
 | ||||||
|  |         if ($action === 'can-i-restart-this-worker') { | ||||||
|  |             $this->isThereAJobInProgress(); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ($action === 'job-status') { | ||||||
|  |             $jobId = text('Which job to check?'); | ||||||
|  |             $jobStatus = $this->getJobStatus($jobId); | ||||||
|  |             $this->info('Job Status: '.$jobStatus); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ($action === 'pending') { | ||||||
|  |             $pendingJobs = app(JobRepository::class)->getPending(); | ||||||
|  |             $pendingJobsTable = []; | ||||||
|  |             if (count($pendingJobs) === 0) { | ||||||
|  |                 $this->info('No pending jobs found.'); | ||||||
|  | 
 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             foreach ($pendingJobs as $pendingJob) { | ||||||
|  |                 $pendingJobsTable[] = [ | ||||||
|  |                     'id' => $pendingJob->id, | ||||||
|  |                     'name' => $pendingJob->name, | ||||||
|  |                     'status' => $pendingJob->status, | ||||||
|  |                     'reserved_at' => $pendingJob->reserved_at ? now()->parse($pendingJob->reserved_at)->format('Y-m-d H:i:s') : null, | ||||||
|  |                 ]; | ||||||
|  |             } | ||||||
|  |             table($pendingJobsTable); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ($action === 'failed') { | ||||||
|  |             $failedJobs = app(JobRepository::class)->getFailed(); | ||||||
|  |             $failedJobsTable = []; | ||||||
|  |             if (count($failedJobs) === 0) { | ||||||
|  |                 $this->info('No failed jobs found.'); | ||||||
|  | 
 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             foreach ($failedJobs as $failedJob) { | ||||||
|  |                 $failedJobsTable[] = [ | ||||||
|  |                     'id' => $failedJob->id, | ||||||
|  |                     'name' => $failedJob->name, | ||||||
|  |                     'failed_at' => $failedJob->failed_at ? now()->parse($failedJob->failed_at)->format('Y-m-d H:i:s') : null, | ||||||
|  |                 ]; | ||||||
|  |             } | ||||||
|  |             table($failedJobsTable); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ($action === 'failed-delete') { | ||||||
|  |             $failedJobs = app(JobRepository::class)->getFailed(); | ||||||
|  |             $failedJobsTable = []; | ||||||
|  |             foreach ($failedJobs as $failedJob) { | ||||||
|  |                 $failedJobsTable[] = [ | ||||||
|  |                     'id' => $failedJob->id, | ||||||
|  |                     'name' => $failedJob->name, | ||||||
|  |                     'failed_at' => $failedJob->failed_at ? now()->parse($failedJob->failed_at)->format('Y-m-d H:i:s') : null, | ||||||
|  |                 ]; | ||||||
|  |             } | ||||||
|  |             app(MetricsRepository::class)->clear(); | ||||||
|  |             if (count($failedJobsTable) === 0) { | ||||||
|  |                 $this->info('No failed jobs found.'); | ||||||
|  | 
 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             $jobIds = multiselect( | ||||||
|  |                 label: 'Which job to delete?', | ||||||
|  |                 options: collect($failedJobsTable)->mapWithKeys(fn ($job) => [$job['id'] => $job['id'].' - '.$job['name']])->toArray(), | ||||||
|  |             ); | ||||||
|  |             foreach ($jobIds as $jobId) { | ||||||
|  |                 Artisan::queue('horizon:forget', ['id' => $jobId]); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ($action === 'running') { | ||||||
|  |             $redisJobRepository = app(CustomJobRepository::class); | ||||||
|  |             $runningJobs = $redisJobRepository->getReservedJobs(); | ||||||
|  |             $runningJobsTable = []; | ||||||
|  |             if (count($runningJobs) === 0) { | ||||||
|  |                 $this->info('No running jobs found.'); | ||||||
|  | 
 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |             foreach ($runningJobs as $runningJob) { | ||||||
|  |                 $runningJobsTable[] = [ | ||||||
|  |                     'id' => $runningJob->id, | ||||||
|  |                     'name' => $runningJob->name, | ||||||
|  |                     'reserved_at' => $runningJob->reserved_at ? now()->parse($runningJob->reserved_at)->format('Y-m-d H:i:s') : null, | ||||||
|  |                 ]; | ||||||
|  |             } | ||||||
|  |             table($runningJobsTable); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ($action === 'workers') { | ||||||
|  |             $redisJobRepository = app(CustomJobRepository::class); | ||||||
|  |             $workers = $redisJobRepository->getHorizonWorkers(); | ||||||
|  |             $workersTable = []; | ||||||
|  |             foreach ($workers as $worker) { | ||||||
|  |                 $workersTable[] = [ | ||||||
|  |                     'name' => $worker->name, | ||||||
|  |                 ]; | ||||||
|  |             } | ||||||
|  |             table($workersTable); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ($action === 'purge-queues') { | ||||||
|  |             $getQueues = app(CustomJobRepository::class)->getQueues(); | ||||||
|  |             $queueName = select( | ||||||
|  |                 label: 'Which queue to purge?', | ||||||
|  |                 options: $getQueues, | ||||||
|  |             ); | ||||||
|  |             $redisJobRepository = app(RedisJobRepository::class); | ||||||
|  |             $redisJobRepository->purge($queueName); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function isThereAJobInProgress() | ||||||
|  |     { | ||||||
|  |         $runningJobs = ApplicationDeploymentQueue::where('horizon_job_worker', gethostname())->where('status', ApplicationDeploymentStatus::IN_PROGRESS->value)->get(); | ||||||
|  |         $count = $runningJobs->count(); | ||||||
|  |         if ($count === 0) { | ||||||
|  |             return false; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function getJobStatus(string $jobId) | ||||||
|  |     { | ||||||
|  |         return getJobStatus($jobId); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -91,7 +91,13 @@ class Kernel extends ConsoleKernel | |||||||
| 
 | 
 | ||||||
|     private function pullImages(): void |     private function pullImages(): void | ||||||
|     { |     { | ||||||
|         $servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get(); |         if (isCloud()) { | ||||||
|  |             $servers = $this->allServers->whereRelation('team.subscription', 'stripe_invoice_paid', true)->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get(); | ||||||
|  |             $own = Team::find(0)->servers; | ||||||
|  |             $servers = $servers->merge($own); | ||||||
|  |         } else { | ||||||
|  |             $servers = $this->allServers->whereRelation('settings', 'is_usable', true)->whereRelation('settings', 'is_reachable', true)->get(); | ||||||
|  |         } | ||||||
|         foreach ($servers as $server) { |         foreach ($servers as $server) { | ||||||
|             if ($server->isSentinelEnabled()) { |             if ($server->isSentinelEnabled()) { | ||||||
|                 $this->scheduleInstance->job(function () use ($server) { |                 $this->scheduleInstance->job(function () use ($server) { | ||||||
| @@ -124,7 +130,7 @@ class Kernel extends ConsoleKernel | |||||||
|     private function checkResources(): void |     private function checkResources(): void | ||||||
|     { |     { | ||||||
|         if (isCloud()) { |         if (isCloud()) { | ||||||
|             $servers = $this->allServers->whereHas('team.subscription')->get(); |             $servers = $this->allServers->whereRelation('team.subscription', 'stripe_invoice_paid', true)->get(); | ||||||
|             $own = Team::find(0)->servers; |             $own = Team::find(0)->servers; | ||||||
|             $servers = $servers->merge($own); |             $servers = $servers->merge($own); | ||||||
|         } else { |         } else { | ||||||
| @@ -133,14 +139,14 @@ class Kernel extends ConsoleKernel | |||||||
| 
 | 
 | ||||||
|         foreach ($servers as $server) { |         foreach ($servers as $server) { | ||||||
|             $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); |             $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); | ||||||
|  |             if (validate_timezone($serverTimezone) === false) { | ||||||
|  |                 $serverTimezone = config('app.timezone'); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             // Sentinel check
 |             // Sentinel check
 | ||||||
|             $lastSentinelUpdate = $server->sentinel_updated_at; |             $lastSentinelUpdate = $server->sentinel_updated_at; | ||||||
|             if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) { |             if (Carbon::parse($lastSentinelUpdate)->isBefore(now()->subSeconds($server->waitBeforeDoingSshCheck()))) { | ||||||
|                 // Check container status every minute if Sentinel does not activated
 |                 // Check container status every minute if Sentinel does not activated
 | ||||||
|                 if (validate_timezone($serverTimezone) === false) { |  | ||||||
|                     $serverTimezone = config('app.timezone'); |  | ||||||
|                 } |  | ||||||
|                 if (isCloud()) { |                 if (isCloud()) { | ||||||
|                     $this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyFiveMinutes()->onOneServer(); |                     $this->scheduleInstance->job(new ServerCheckJob($server))->timezone($serverTimezone)->everyFiveMinutes()->onOneServer(); | ||||||
|                 } else { |                 } else { | ||||||
| @@ -148,15 +154,11 @@ class Kernel extends ConsoleKernel | |||||||
|                 } |                 } | ||||||
|                 // $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyFiveMinutes()->onOneServer();
 |                 // $this->scheduleInstance->job(new \App\Jobs\ServerCheckNewJob($server))->everyFiveMinutes()->onOneServer();
 | ||||||
| 
 | 
 | ||||||
|                 // Check storage usage every 10 minutes if Sentinel does not activated
 |                 $this->scheduleInstance->job(new ServerStorageCheckJob($server))->cron($server->settings->server_disk_usage_check_frequency)->timezone($serverTimezone)->onOneServer(); | ||||||
|                 $this->scheduleInstance->job(new ServerStorageCheckJob($server))->everyTenMinutes()->onOneServer(); |  | ||||||
|             } |  | ||||||
|             if ($server->settings->force_docker_cleanup) { |  | ||||||
|                 $this->scheduleInstance->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer(); |  | ||||||
|             } else { |  | ||||||
|                 $this->scheduleInstance->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer(); |  | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             $this->scheduleInstance->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer(); | ||||||
|  | 
 | ||||||
|             // Cleanup multiplexed connections every hour
 |             // Cleanup multiplexed connections every hour
 | ||||||
|             // $this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer();
 |             // $this->scheduleInstance->job(new ServerCleanupMux($server))->hourly()->onOneServer();
 | ||||||
| 
 | 
 | ||||||
| @@ -175,25 +177,47 @@ class Kernel extends ConsoleKernel | |||||||
|         if ($scheduled_backups->isEmpty()) { |         if ($scheduled_backups->isEmpty()) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |         $finalScheduledBackups = collect(); | ||||||
|         foreach ($scheduled_backups as $scheduled_backup) { |         foreach ($scheduled_backups as $scheduled_backup) { | ||||||
|             if (is_null(data_get($scheduled_backup, 'database'))) { |             if (blank(data_get($scheduled_backup, 'database'))) { | ||||||
|                 $scheduled_backup->delete(); |                 $scheduled_backup->delete(); | ||||||
| 
 | 
 | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|  |             $server = $scheduled_backup->server(); | ||||||
|  |             if (blank($server)) { | ||||||
|  |                 $scheduled_backup->delete(); | ||||||
|  | 
 | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             if ($server->isFunctional() === false) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             if (isCloud() && data_get($server->team->subscription, 'stripe_invoice_paid', false) === false && $server->team->id !== 0) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             $finalScheduledBackups->push($scheduled_backup); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         foreach ($finalScheduledBackups as $scheduled_backup) { | ||||||
|  |             if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) { | ||||||
|  |                 $scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency]; | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             $server = $scheduled_backup->server(); |             $server = $scheduled_backup->server(); | ||||||
|  |             $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); | ||||||
| 
 | 
 | ||||||
|             if (is_null($server)) { |             if (validate_timezone($serverTimezone) === false) { | ||||||
|                 continue; |                 $serverTimezone = config('app.timezone'); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) { |             if (isset(VALID_CRON_STRINGS[$scheduled_backup->frequency])) { | ||||||
|                 $scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency]; |                 $scheduled_backup->frequency = VALID_CRON_STRINGS[$scheduled_backup->frequency]; | ||||||
|             } |             } | ||||||
|  |             $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); | ||||||
|             $this->scheduleInstance->job(new DatabaseBackupJob( |             $this->scheduleInstance->job(new DatabaseBackupJob( | ||||||
|                 backup: $scheduled_backup |                 backup: $scheduled_backup | ||||||
|             ))->cron($scheduled_backup->frequency)->timezone($this->instanceTimezone)->onOneServer(); |             ))->cron($scheduled_backup->frequency)->timezone($serverTimezone)->onOneServer(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -203,37 +227,55 @@ class Kernel extends ConsoleKernel | |||||||
|         if ($scheduled_tasks->isEmpty()) { |         if ($scheduled_tasks->isEmpty()) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|  |         $finalScheduledTasks = collect(); | ||||||
|         foreach ($scheduled_tasks as $scheduled_task) { |         foreach ($scheduled_tasks as $scheduled_task) { | ||||||
|             $service = $scheduled_task->service; |             $service = $scheduled_task->service; | ||||||
|             $application = $scheduled_task->application; |             $application = $scheduled_task->application; | ||||||
| 
 | 
 | ||||||
|             if (! $application && ! $service) { |             $server = $scheduled_task->server(); | ||||||
|  |             if (blank($server)) { | ||||||
|                 $scheduled_task->delete(); |                 $scheduled_task->delete(); | ||||||
| 
 | 
 | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
|             if ($application) { |  | ||||||
|                 if (str($application->status)->contains('running') === false) { |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|             if ($service) { |  | ||||||
|                 if (str($service->status)->contains('running') === false) { |  | ||||||
|                     continue; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             $server = $scheduled_task->server(); |             if ($server->isFunctional() === false) { | ||||||
|             if (! $server) { |  | ||||||
|                 continue; |                 continue; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  |             if (isCloud() && data_get($server->team->subscription, 'stripe_invoice_paid', false) === false && $server->team->id !== 0) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if (! $service && ! $application) { | ||||||
|  |                 $scheduled_task->delete(); | ||||||
|  | 
 | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if ($application && str($application->status)->contains('running') === false) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  |             if ($service && str($service->status)->contains('running') === false) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $finalScheduledTasks->push($scheduled_task); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         foreach ($finalScheduledTasks as $scheduled_task) { | ||||||
|  |             $server = $scheduled_task->server(); | ||||||
|             if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) { |             if (isset(VALID_CRON_STRINGS[$scheduled_task->frequency])) { | ||||||
|                 $scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency]; |                 $scheduled_task->frequency = VALID_CRON_STRINGS[$scheduled_task->frequency]; | ||||||
|             } |             } | ||||||
|  |             $serverTimezone = data_get($server->settings, 'server_timezone', $this->instanceTimezone); | ||||||
|  | 
 | ||||||
|  |             if (validate_timezone($serverTimezone) === false) { | ||||||
|  |                 $serverTimezone = config('app.timezone'); | ||||||
|  |             } | ||||||
|             $this->scheduleInstance->job(new ScheduledTaskJob( |             $this->scheduleInstance->job(new ScheduledTaskJob( | ||||||
|                 task: $scheduled_task |                 task: $scheduled_task | ||||||
|             ))->cron($scheduled_task->frequency)->timezone($this->instanceTimezone)->onOneServer(); |             ))->cron($scheduled_task->frequency)->timezone($serverTimezone)->onOneServer(); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								app/Contracts/CustomJobRepositoryInterface.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								app/Contracts/CustomJobRepositoryInterface.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Contracts; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Support\Collection; | ||||||
|  | use Laravel\Horizon\Contracts\JobRepository; | ||||||
|  | 
 | ||||||
|  | interface CustomJobRepositoryInterface extends JobRepository | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Get all jobs with a specific status. | ||||||
|  |      */ | ||||||
|  |     public function getJobsByStatus(string $status): Collection; | ||||||
|  | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the count of jobs with a specific status. | ||||||
|  |      */ | ||||||
|  |     public function countJobsByStatus(string $status): int; | ||||||
|  | } | ||||||
							
								
								
									
										34
									
								
								app/Events/RestoreJobFinished.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								app/Events/RestoreJobFinished.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,34 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Events; | ||||||
|  | 
 | ||||||
|  | use App\Models\Server; | ||||||
|  | use Illuminate\Broadcasting\InteractsWithSockets; | ||||||
|  | use Illuminate\Foundation\Events\Dispatchable; | ||||||
|  | use Illuminate\Queue\SerializesModels; | ||||||
|  | 
 | ||||||
|  | class RestoreJobFinished | ||||||
|  | { | ||||||
|  |     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||||
|  | 
 | ||||||
|  |     public function __construct($data) | ||||||
|  |     { | ||||||
|  |         $scriptPath = data_get($data, 'scriptPath'); | ||||||
|  |         $tmpPath = data_get($data, 'tmpPath'); | ||||||
|  |         $container = data_get($data, 'container'); | ||||||
|  |         $serverId = data_get($data, 'serverId'); | ||||||
|  |         if (filled($scriptPath) && filled($tmpPath) && filled($container) && filled($serverId)) { | ||||||
|  |             if (str($tmpPath)->startsWith('/tmp/') | ||||||
|  |                 && str($scriptPath)->startsWith('/tmp/') | ||||||
|  |                 && ! str($tmpPath)->contains('..') | ||||||
|  |                 && ! str($scriptPath)->contains('..') | ||||||
|  |                 && strlen($tmpPath) > 5  // longer than just "/tmp/"
 | ||||||
|  |                 && strlen($scriptPath) > 5 | ||||||
|  |             ) { | ||||||
|  |                 $commands[] = "docker exec {$container} sh -c 'rm {$scriptPath}'"; | ||||||
|  |                 $commands[] = "docker exec {$container} sh -c 'rm {$tmpPath}'"; | ||||||
|  |                 instant_remote_process($commands, Server::find($serverId), throwError: true); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -27,6 +27,9 @@ class ApplicationsController extends Controller | |||||||
|     { |     { | ||||||
|         $application->makeHidden([ |         $application->makeHidden([ | ||||||
|             'id', |             'id', | ||||||
|  |             'resourceable', | ||||||
|  |             'resourceable_id', | ||||||
|  |             'resourceable_type', | ||||||
|         ]); |         ]); | ||||||
|         if (request()->attributes->get('can_read_sensitive', false) === false) { |         if (request()->attributes->get('can_read_sensitive', false) === false) { | ||||||
|             $application->makeHidden([ |             $application->makeHidden([ | ||||||
| @@ -114,11 +117,12 @@ class ApplicationsController extends Controller | |||||||
|                     mediaType: 'application/json', |                     mediaType: 'application/json', | ||||||
|                     schema: new OA\Schema( |                     schema: new OA\Schema( | ||||||
|                         type: 'object', |                         type: 'object', | ||||||
|                         required: ['project_uuid', 'server_uuid', 'environment_name', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'], |                         required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'], | ||||||
|                         properties: [ |                         properties: [ | ||||||
|                             'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], |                             'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], | ||||||
|                             'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], |                             'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], | ||||||
|                             'environment_name' => ['type' => 'string', 'description' => 'The environment name.'], |                             'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|  |                             'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|                             'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'], |                             'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'], | ||||||
|                             'git_branch' => ['type' => 'string', 'description' => 'The git branch.'], |                             'git_branch' => ['type' => 'string', 'description' => 'The git branch.'], | ||||||
|                             'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'], |                             'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'], | ||||||
| @@ -185,8 +189,17 @@ class ApplicationsController extends Controller | |||||||
|         ), |         ), | ||||||
|         responses: [ |         responses: [ | ||||||
|             new OA\Response( |             new OA\Response( | ||||||
|                 response: 200, |                 response: 201, | ||||||
|                 description: 'Application created successfully.', |                 description: 'Application created successfully.', | ||||||
|  |                 content: new OA\MediaType( | ||||||
|  |                     mediaType: 'application/json', | ||||||
|  |                     schema: new OA\Schema( | ||||||
|  |                         type: 'object', | ||||||
|  |                         properties: [ | ||||||
|  |                             'uuid' => ['type' => 'string'], | ||||||
|  |                         ] | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|             ), |             ), | ||||||
|             new OA\Response( |             new OA\Response( | ||||||
|                 response: 401, |                 response: 401, | ||||||
| @@ -220,11 +233,12 @@ class ApplicationsController extends Controller | |||||||
|                     mediaType: 'application/json', |                     mediaType: 'application/json', | ||||||
|                     schema: new OA\Schema( |                     schema: new OA\Schema( | ||||||
|                         type: 'object', |                         type: 'object', | ||||||
|                         required: ['project_uuid', 'server_uuid', 'environment_name', 'github_app_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'], |                         required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'github_app_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'], | ||||||
|                         properties: [ |                         properties: [ | ||||||
|                             'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], |                             'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], | ||||||
|                             'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], |                             'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], | ||||||
|                             'environment_name' => ['type' => 'string', 'description' => 'The environment name.'], |                             'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|  |                             'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|                             'github_app_uuid' => ['type' => 'string', 'description' => 'The Github App UUID.'], |                             'github_app_uuid' => ['type' => 'string', 'description' => 'The Github App UUID.'], | ||||||
|                             'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'], |                             'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'], | ||||||
|                             'git_branch' => ['type' => 'string', 'description' => 'The git branch.'], |                             'git_branch' => ['type' => 'string', 'description' => 'The git branch.'], | ||||||
| @@ -291,8 +305,17 @@ class ApplicationsController extends Controller | |||||||
|         ), |         ), | ||||||
|         responses: [ |         responses: [ | ||||||
|             new OA\Response( |             new OA\Response( | ||||||
|                 response: 200, |                 response: 201, | ||||||
|                 description: 'Application created successfully.', |                 description: 'Application created successfully.', | ||||||
|  |                 content: new OA\MediaType( | ||||||
|  |                     mediaType: 'application/json', | ||||||
|  |                     schema: new OA\Schema( | ||||||
|  |                         type: 'object', | ||||||
|  |                         properties: [ | ||||||
|  |                             'uuid' => ['type' => 'string'], | ||||||
|  |                         ] | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|             ), |             ), | ||||||
|             new OA\Response( |             new OA\Response( | ||||||
|                 response: 401, |                 response: 401, | ||||||
| @@ -326,11 +349,12 @@ class ApplicationsController extends Controller | |||||||
|                     mediaType: 'application/json', |                     mediaType: 'application/json', | ||||||
|                     schema: new OA\Schema( |                     schema: new OA\Schema( | ||||||
|                         type: 'object', |                         type: 'object', | ||||||
|                         required: ['project_uuid', 'server_uuid', 'environment_name', 'private_key_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'], |                         required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'private_key_uuid', 'git_repository', 'git_branch', 'build_pack', 'ports_exposes'], | ||||||
|                         properties: [ |                         properties: [ | ||||||
|                             'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], |                             'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], | ||||||
|                             'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], |                             'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], | ||||||
|                             'environment_name' => ['type' => 'string', 'description' => 'The environment name.'], |                             'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|  |                             'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|                             'private_key_uuid' => ['type' => 'string', 'description' => 'The private key UUID.'], |                             'private_key_uuid' => ['type' => 'string', 'description' => 'The private key UUID.'], | ||||||
|                             'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'], |                             'git_repository' => ['type' => 'string', 'description' => 'The git repository URL.'], | ||||||
|                             'git_branch' => ['type' => 'string', 'description' => 'The git branch.'], |                             'git_branch' => ['type' => 'string', 'description' => 'The git branch.'], | ||||||
| @@ -397,8 +421,17 @@ class ApplicationsController extends Controller | |||||||
|         ), |         ), | ||||||
|         responses: [ |         responses: [ | ||||||
|             new OA\Response( |             new OA\Response( | ||||||
|                 response: 200, |                 response: 201, | ||||||
|                 description: 'Application created successfully.', |                 description: 'Application created successfully.', | ||||||
|  |                 content: new OA\MediaType( | ||||||
|  |                     mediaType: 'application/json', | ||||||
|  |                     schema: new OA\Schema( | ||||||
|  |                         type: 'object', | ||||||
|  |                         properties: [ | ||||||
|  |                             'uuid' => ['type' => 'string'], | ||||||
|  |                         ] | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|             ), |             ), | ||||||
|             new OA\Response( |             new OA\Response( | ||||||
|                 response: 401, |                 response: 401, | ||||||
| @@ -432,11 +465,12 @@ class ApplicationsController extends Controller | |||||||
|                     mediaType: 'application/json', |                     mediaType: 'application/json', | ||||||
|                     schema: new OA\Schema( |                     schema: new OA\Schema( | ||||||
|                         type: 'object', |                         type: 'object', | ||||||
|                         required: ['project_uuid', 'server_uuid', 'environment_name', 'dockerfile'], |                         required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'dockerfile'], | ||||||
|                         properties: [ |                         properties: [ | ||||||
|                             'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], |                             'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], | ||||||
|                             'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], |                             'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], | ||||||
|                             'environment_name' => ['type' => 'string', 'description' => 'The environment name.'], |                             'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|  |                             'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|                             'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'], |                             'dockerfile' => ['type' => 'string', 'description' => 'The Dockerfile content.'], | ||||||
|                             'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'], |                             'build_pack' => ['type' => 'string', 'enum' => ['nixpacks', 'static', 'dockerfile', 'dockercompose'], 'description' => 'The build pack type.'], | ||||||
|                             'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'], |                             'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'], | ||||||
| @@ -487,8 +521,17 @@ class ApplicationsController extends Controller | |||||||
|         ), |         ), | ||||||
|         responses: [ |         responses: [ | ||||||
|             new OA\Response( |             new OA\Response( | ||||||
|                 response: 200, |                 response: 201, | ||||||
|                 description: 'Application created successfully.', |                 description: 'Application created successfully.', | ||||||
|  |                 content: new OA\MediaType( | ||||||
|  |                     mediaType: 'application/json', | ||||||
|  |                     schema: new OA\Schema( | ||||||
|  |                         type: 'object', | ||||||
|  |                         properties: [ | ||||||
|  |                             'uuid' => ['type' => 'string'], | ||||||
|  |                         ] | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|             ), |             ), | ||||||
|             new OA\Response( |             new OA\Response( | ||||||
|                 response: 401, |                 response: 401, | ||||||
| @@ -522,11 +565,12 @@ class ApplicationsController extends Controller | |||||||
|                     mediaType: 'application/json', |                     mediaType: 'application/json', | ||||||
|                     schema: new OA\Schema( |                     schema: new OA\Schema( | ||||||
|                         type: 'object', |                         type: 'object', | ||||||
|                         required: ['project_uuid', 'server_uuid', 'environment_name', 'docker_registry_image_name', 'ports_exposes'], |                         required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'docker_registry_image_name', 'ports_exposes'], | ||||||
|                         properties: [ |                         properties: [ | ||||||
|                             'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], |                             'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], | ||||||
|                             'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], |                             'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], | ||||||
|                             'environment_name' => ['type' => 'string', 'description' => 'The environment name.'], |                             'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|  |                             'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|                             'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'], |                             'docker_registry_image_name' => ['type' => 'string', 'description' => 'The docker registry image name.'], | ||||||
|                             'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'], |                             'docker_registry_image_tag' => ['type' => 'string', 'description' => 'The docker registry image tag.'], | ||||||
|                             'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'], |                             'ports_exposes' => ['type' => 'string', 'description' => 'The ports to expose.'], | ||||||
| @@ -574,8 +618,17 @@ class ApplicationsController extends Controller | |||||||
|         ), |         ), | ||||||
|         responses: [ |         responses: [ | ||||||
|             new OA\Response( |             new OA\Response( | ||||||
|                 response: 200, |                 response: 201, | ||||||
|                 description: 'Application created successfully.', |                 description: 'Application created successfully.', | ||||||
|  |                 content: new OA\MediaType( | ||||||
|  |                     mediaType: 'application/json', | ||||||
|  |                     schema: new OA\Schema( | ||||||
|  |                         type: 'object', | ||||||
|  |                         properties: [ | ||||||
|  |                             'uuid' => ['type' => 'string'], | ||||||
|  |                         ] | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|             ), |             ), | ||||||
|             new OA\Response( |             new OA\Response( | ||||||
|                 response: 401, |                 response: 401, | ||||||
| @@ -609,11 +662,12 @@ class ApplicationsController extends Controller | |||||||
|                     mediaType: 'application/json', |                     mediaType: 'application/json', | ||||||
|                     schema: new OA\Schema( |                     schema: new OA\Schema( | ||||||
|                         type: 'object', |                         type: 'object', | ||||||
|                         required: ['project_uuid', 'server_uuid', 'environment_name', 'docker_compose_raw'], |                         required: ['project_uuid', 'server_uuid', 'environment_name', 'environment_uuid', 'docker_compose_raw'], | ||||||
|                         properties: [ |                         properties: [ | ||||||
|                             'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], |                             'project_uuid' => ['type' => 'string', 'description' => 'The project UUID.'], | ||||||
|                             'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], |                             'server_uuid' => ['type' => 'string', 'description' => 'The server UUID.'], | ||||||
|                             'environment_name' => ['type' => 'string', 'description' => 'The environment name.'], |                             'environment_name' => ['type' => 'string', 'description' => 'The environment name. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|  |                             'environment_uuid' => ['type' => 'string', 'description' => 'The environment UUID. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|                             'docker_compose_raw' => ['type' => 'string', 'description' => 'The Docker Compose raw content.'], |                             'docker_compose_raw' => ['type' => 'string', 'description' => 'The Docker Compose raw content.'], | ||||||
|                             'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID if the server has more than one destinations.'], |                             'destination_uuid' => ['type' => 'string', 'description' => 'The destination UUID if the server has more than one destinations.'], | ||||||
|                             'name' => ['type' => 'string', 'description' => 'The application name.'], |                             'name' => ['type' => 'string', 'description' => 'The application name.'], | ||||||
| @@ -627,8 +681,17 @@ class ApplicationsController extends Controller | |||||||
|         ), |         ), | ||||||
|         responses: [ |         responses: [ | ||||||
|             new OA\Response( |             new OA\Response( | ||||||
|                 response: 200, |                 response: 201, | ||||||
|                 description: 'Application created successfully.', |                 description: 'Application created successfully.', | ||||||
|  |                 content: new OA\MediaType( | ||||||
|  |                     mediaType: 'application/json', | ||||||
|  |                     schema: new OA\Schema( | ||||||
|  |                         type: 'object', | ||||||
|  |                         properties: [ | ||||||
|  |                             'uuid' => ['type' => 'string'], | ||||||
|  |                         ] | ||||||
|  |                     ) | ||||||
|  |                 ) | ||||||
|             ), |             ), | ||||||
|             new OA\Response( |             new OA\Response( | ||||||
|                 response: 401, |                 response: 401, | ||||||
| @@ -647,7 +710,7 @@ class ApplicationsController extends Controller | |||||||
| 
 | 
 | ||||||
|     private function create_application(Request $request, $type) |     private function create_application(Request $request, $type) | ||||||
|     { |     { | ||||||
|         $allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container',  'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration']; |         $allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'private_key_uuid', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container',  'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'redirect', 'github_app_uuid', 'instant_deploy', 'dockerfile', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'watch_paths', 'use_build_server', 'static_image', 'custom_nginx_configuration']; | ||||||
|         $teamId = getTeamIdFromToken(); |         $teamId = getTeamIdFromToken(); | ||||||
|         if (is_null($teamId)) { |         if (is_null($teamId)) { | ||||||
|             return invalidTokenResponse(); |             return invalidTokenResponse(); | ||||||
| @@ -661,7 +724,8 @@ class ApplicationsController extends Controller | |||||||
|             'name' => 'string|max:255', |             'name' => 'string|max:255', | ||||||
|             'description' => 'string|nullable', |             'description' => 'string|nullable', | ||||||
|             'project_uuid' => 'string|required', |             'project_uuid' => 'string|required', | ||||||
|             'environment_name' => 'string|required', |             'environment_name' => 'string|nullable', | ||||||
|  |             'environment_uuid' => 'string|nullable', | ||||||
|             'server_uuid' => 'string|required', |             'server_uuid' => 'string|required', | ||||||
|             'destination_uuid' => 'string', |             'destination_uuid' => 'string', | ||||||
|         ]); |         ]); | ||||||
| @@ -681,6 +745,11 @@ class ApplicationsController extends Controller | |||||||
|             ], 422); |             ], 422); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         $environmentUuid = $request->environment_uuid; | ||||||
|  |         $environmentName = $request->environment_name; | ||||||
|  |         if (blank($environmentUuid) && blank($environmentName)) { | ||||||
|  |             return response()->json(['message' => 'You need to provide at least one of environment_name or environment_uuid.'], 422); | ||||||
|  |         } | ||||||
|         $serverUuid = $request->server_uuid; |         $serverUuid = $request->server_uuid; | ||||||
|         $fqdn = $request->domains; |         $fqdn = $request->domains; | ||||||
|         $instantDeploy = $request->instant_deploy; |         $instantDeploy = $request->instant_deploy; | ||||||
| @@ -713,7 +782,10 @@ class ApplicationsController extends Controller | |||||||
|         if (! $project) { |         if (! $project) { | ||||||
|             return response()->json(['message' => 'Project not found.'], 404); |             return response()->json(['message' => 'Project not found.'], 404); | ||||||
|         } |         } | ||||||
|         $environment = $project->environments()->where('name', $request->environment_name)->first(); |         $environment = $project->environments()->where('name', $environmentName)->first(); | ||||||
|  |         if (! $environment) { | ||||||
|  |             $environment = $project->environments()->where('uuid', $environmentUuid)->first(); | ||||||
|  |         } | ||||||
|         if (! $environment) { |         if (! $environment) { | ||||||
|             return response()->json(['message' => 'Environment not found.'], 404); |             return response()->json(['message' => 'Environment not found.'], 404); | ||||||
|         } |         } | ||||||
| @@ -730,12 +802,6 @@ class ApplicationsController extends Controller | |||||||
|         } |         } | ||||||
|         $destination = $destinations->first(); |         $destination = $destinations->first(); | ||||||
|         if ($type === 'public') { |         if ($type === 'public') { | ||||||
|             if (! $request->has('name')) { |  | ||||||
|                 $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch)); |  | ||||||
|             } |  | ||||||
|             if ($request->build_pack === 'dockercompose') { |  | ||||||
|                 $request->offsetSet('ports_exposes', '80'); |  | ||||||
|             } |  | ||||||
|             $validationRules = [ |             $validationRules = [ | ||||||
|                 'git_repository' => 'string|required', |                 'git_repository' => 'string|required', | ||||||
|                 'git_branch' => 'string|required', |                 'git_branch' => 'string|required', | ||||||
| @@ -745,7 +811,7 @@ class ApplicationsController extends Controller | |||||||
|                 'docker_compose_raw' => 'string|nullable', |                 'docker_compose_raw' => 'string|nullable', | ||||||
|                 'docker_compose_domains' => 'array|nullable', |                 'docker_compose_domains' => 'array|nullable', | ||||||
|             ]; |             ]; | ||||||
|             $validationRules = array_merge($validationRules, sharedDataApplications()); |             $validationRules = array_merge(sharedDataApplications(), $validationRules); | ||||||
|             $validator = customApiValidator($request->all(), $validationRules); |             $validator = customApiValidator($request->all(), $validationRules); | ||||||
|             if ($validator->fails()) { |             if ($validator->fails()) { | ||||||
|                 return response()->json([ |                 return response()->json([ | ||||||
| @@ -753,6 +819,12 @@ class ApplicationsController extends Controller | |||||||
|                     'errors' => $validator->errors(), |                     'errors' => $validator->errors(), | ||||||
|                 ], 422); |                 ], 422); | ||||||
|             } |             } | ||||||
|  |             if (! $request->has('name')) { | ||||||
|  |                 $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch)); | ||||||
|  |             } | ||||||
|  |             if ($request->build_pack === 'dockercompose') { | ||||||
|  |                 $request->offsetSet('ports_exposes', '80'); | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|             $return = $this->validateDataApplications($request, $server); |             $return = $this->validateDataApplications($request, $server); | ||||||
|             if ($return instanceof \Illuminate\Http\JsonResponse) { |             if ($return instanceof \Illuminate\Http\JsonResponse) { | ||||||
| @@ -791,7 +863,7 @@ class ApplicationsController extends Controller | |||||||
|                 $application->settings->save(); |                 $application->settings->save(); | ||||||
|             } |             } | ||||||
|             $application->refresh(); |             $application->refresh(); | ||||||
|             if (! $application->settings->is_container_label_readonly_enabled) { |             if ($application->settings->is_container_label_readonly_enabled) { | ||||||
|                 $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); |                 $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); | ||||||
|                 $application->save(); |                 $application->save(); | ||||||
|             } |             } | ||||||
| @@ -815,14 +887,8 @@ class ApplicationsController extends Controller | |||||||
|             return response()->json(serializeApiResponse([ |             return response()->json(serializeApiResponse([ | ||||||
|                 'uuid' => data_get($application, 'uuid'), |                 'uuid' => data_get($application, 'uuid'), | ||||||
|                 'domains' => data_get($application, 'domains'), |                 'domains' => data_get($application, 'domains'), | ||||||
|             ])); |             ]))->setStatusCode(201); | ||||||
|         } elseif ($type === 'private-gh-app') { |         } elseif ($type === 'private-gh-app') { | ||||||
|             if (! $request->has('name')) { |  | ||||||
|                 $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch)); |  | ||||||
|             } |  | ||||||
|             if ($request->build_pack === 'dockercompose') { |  | ||||||
|                 $request->offsetSet('ports_exposes', '80'); |  | ||||||
|             } |  | ||||||
|             $validationRules = [ |             $validationRules = [ | ||||||
|                 'git_repository' => 'string|required', |                 'git_repository' => 'string|required', | ||||||
|                 'git_branch' => 'string|required', |                 'git_branch' => 'string|required', | ||||||
| @@ -833,7 +899,7 @@ class ApplicationsController extends Controller | |||||||
|                 'docker_compose_location' => 'string', |                 'docker_compose_location' => 'string', | ||||||
|                 'docker_compose_raw' => 'string|nullable', |                 'docker_compose_raw' => 'string|nullable', | ||||||
|             ]; |             ]; | ||||||
|             $validationRules = array_merge($validationRules, sharedDataApplications()); |             $validationRules = array_merge(sharedDataApplications(), $validationRules); | ||||||
| 
 | 
 | ||||||
|             $validator = customApiValidator($request->all(), $validationRules); |             $validator = customApiValidator($request->all(), $validationRules); | ||||||
|             if ($validator->fails()) { |             if ($validator->fails()) { | ||||||
| @@ -842,6 +908,14 @@ class ApplicationsController extends Controller | |||||||
|                     'errors' => $validator->errors(), |                     'errors' => $validator->errors(), | ||||||
|                 ], 422); |                 ], 422); | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             if (! $request->has('name')) { | ||||||
|  |                 $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch)); | ||||||
|  |             } | ||||||
|  |             if ($request->build_pack === 'dockercompose') { | ||||||
|  |                 $request->offsetSet('ports_exposes', '80'); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             $return = $this->validateDataApplications($request, $server); |             $return = $this->validateDataApplications($request, $server); | ||||||
|             if ($return instanceof \Illuminate\Http\JsonResponse) { |             if ($return instanceof \Illuminate\Http\JsonResponse) { | ||||||
|                 return $return; |                 return $return; | ||||||
| @@ -884,13 +958,13 @@ class ApplicationsController extends Controller | |||||||
|             $application->environment_id = $environment->id; |             $application->environment_id = $environment->id; | ||||||
|             $application->source_type = $githubApp->getMorphClass(); |             $application->source_type = $githubApp->getMorphClass(); | ||||||
|             $application->source_id = $githubApp->id; |             $application->source_id = $githubApp->id; | ||||||
|  |             $application->save(); | ||||||
|  |             $application->refresh(); | ||||||
|             if (isset($useBuildServer)) { |             if (isset($useBuildServer)) { | ||||||
|                 $application->settings->is_build_server_enabled = $useBuildServer; |                 $application->settings->is_build_server_enabled = $useBuildServer; | ||||||
|                 $application->settings->save(); |                 $application->settings->save(); | ||||||
|             } |             } | ||||||
|             $application->save(); |             if ($application->settings->is_container_label_readonly_enabled) { | ||||||
|             $application->refresh(); |  | ||||||
|             if (! $application->settings->is_container_label_readonly_enabled) { |  | ||||||
|                 $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); |                 $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); | ||||||
|                 $application->save(); |                 $application->save(); | ||||||
|             } |             } | ||||||
| @@ -914,14 +988,8 @@ class ApplicationsController extends Controller | |||||||
|             return response()->json(serializeApiResponse([ |             return response()->json(serializeApiResponse([ | ||||||
|                 'uuid' => data_get($application, 'uuid'), |                 'uuid' => data_get($application, 'uuid'), | ||||||
|                 'domains' => data_get($application, 'domains'), |                 'domains' => data_get($application, 'domains'), | ||||||
|             ])); |             ]))->setStatusCode(201); | ||||||
|         } elseif ($type === 'private-deploy-key') { |         } elseif ($type === 'private-deploy-key') { | ||||||
|             if (! $request->has('name')) { |  | ||||||
|                 $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch)); |  | ||||||
|             } |  | ||||||
|             if ($request->build_pack === 'dockercompose') { |  | ||||||
|                 $request->offsetSet('ports_exposes', '80'); |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             $validationRules = [ |             $validationRules = [ | ||||||
|                 'git_repository' => 'string|required', |                 'git_repository' => 'string|required', | ||||||
| @@ -934,7 +1002,7 @@ class ApplicationsController extends Controller | |||||||
|                 'docker_compose_raw' => 'string|nullable', |                 'docker_compose_raw' => 'string|nullable', | ||||||
|             ]; |             ]; | ||||||
| 
 | 
 | ||||||
|             $validationRules = array_merge($validationRules, sharedDataApplications()); |             $validationRules = array_merge(sharedDataApplications(), $validationRules); | ||||||
|             $validator = customApiValidator($request->all(), $validationRules); |             $validator = customApiValidator($request->all(), $validationRules); | ||||||
| 
 | 
 | ||||||
|             if ($validator->fails()) { |             if ($validator->fails()) { | ||||||
| @@ -943,6 +1011,13 @@ class ApplicationsController extends Controller | |||||||
|                     'errors' => $validator->errors(), |                     'errors' => $validator->errors(), | ||||||
|                 ], 422); |                 ], 422); | ||||||
|             } |             } | ||||||
|  |             if (! $request->has('name')) { | ||||||
|  |                 $request->offsetSet('name', generate_application_name($request->git_repository, $request->git_branch)); | ||||||
|  |             } | ||||||
|  |             if ($request->build_pack === 'dockercompose') { | ||||||
|  |                 $request->offsetSet('ports_exposes', '80'); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             $return = $this->validateDataApplications($request, $server); |             $return = $this->validateDataApplications($request, $server); | ||||||
|             if ($return instanceof \Illuminate\Http\JsonResponse) { |             if ($return instanceof \Illuminate\Http\JsonResponse) { | ||||||
|                 return $return; |                 return $return; | ||||||
| @@ -980,13 +1055,13 @@ class ApplicationsController extends Controller | |||||||
|             $application->destination_id = $destination->id; |             $application->destination_id = $destination->id; | ||||||
|             $application->destination_type = $destination->getMorphClass(); |             $application->destination_type = $destination->getMorphClass(); | ||||||
|             $application->environment_id = $environment->id; |             $application->environment_id = $environment->id; | ||||||
|  |             $application->save(); | ||||||
|  |             $application->refresh(); | ||||||
|             if (isset($useBuildServer)) { |             if (isset($useBuildServer)) { | ||||||
|                 $application->settings->is_build_server_enabled = $useBuildServer; |                 $application->settings->is_build_server_enabled = $useBuildServer; | ||||||
|                 $application->settings->save(); |                 $application->settings->save(); | ||||||
|             } |             } | ||||||
|             $application->save(); |             if ($application->settings->is_container_label_readonly_enabled) { | ||||||
|             $application->refresh(); |  | ||||||
|             if (! $application->settings->is_container_label_readonly_enabled) { |  | ||||||
|                 $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); |                 $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); | ||||||
|                 $application->save(); |                 $application->save(); | ||||||
|             } |             } | ||||||
| @@ -1010,16 +1085,12 @@ class ApplicationsController extends Controller | |||||||
|             return response()->json(serializeApiResponse([ |             return response()->json(serializeApiResponse([ | ||||||
|                 'uuid' => data_get($application, 'uuid'), |                 'uuid' => data_get($application, 'uuid'), | ||||||
|                 'domains' => data_get($application, 'domains'), |                 'domains' => data_get($application, 'domains'), | ||||||
|             ])); |             ]))->setStatusCode(201); | ||||||
|         } elseif ($type === 'dockerfile') { |         } elseif ($type === 'dockerfile') { | ||||||
|             if (! $request->has('name')) { |  | ||||||
|                 $request->offsetSet('name', 'dockerfile-'.new Cuid2); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             $validationRules = [ |             $validationRules = [ | ||||||
|                 'dockerfile' => 'string|required', |                 'dockerfile' => 'string|required', | ||||||
|             ]; |             ]; | ||||||
|             $validationRules = array_merge($validationRules, sharedDataApplications()); |             $validationRules = array_merge(sharedDataApplications(), $validationRules); | ||||||
|             $validator = customApiValidator($request->all(), $validationRules); |             $validator = customApiValidator($request->all(), $validationRules); | ||||||
| 
 | 
 | ||||||
|             if ($validator->fails()) { |             if ($validator->fails()) { | ||||||
| @@ -1028,6 +1099,10 @@ class ApplicationsController extends Controller | |||||||
|                     'errors' => $validator->errors(), |                     'errors' => $validator->errors(), | ||||||
|                 ], 422); |                 ], 422); | ||||||
|             } |             } | ||||||
|  |             if (! $request->has('name')) { | ||||||
|  |                 $request->offsetSet('name', 'dockerfile-'.new Cuid2); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             $return = $this->validateDataApplications($request, $server); |             $return = $this->validateDataApplications($request, $server); | ||||||
|             if ($return instanceof \Illuminate\Http\JsonResponse) { |             if ($return instanceof \Illuminate\Http\JsonResponse) { | ||||||
|                 return $return; |                 return $return; | ||||||
| @@ -1066,16 +1141,16 @@ class ApplicationsController extends Controller | |||||||
|             $application->destination_id = $destination->id; |             $application->destination_id = $destination->id; | ||||||
|             $application->destination_type = $destination->getMorphClass(); |             $application->destination_type = $destination->getMorphClass(); | ||||||
|             $application->environment_id = $environment->id; |             $application->environment_id = $environment->id; | ||||||
|             if (isset($useBuildServer)) { |  | ||||||
|                 $application->settings->is_build_server_enabled = $useBuildServer; |  | ||||||
|                 $application->settings->save(); |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             $application->git_repository = 'coollabsio/coolify'; |             $application->git_repository = 'coollabsio/coolify'; | ||||||
|             $application->git_branch = 'main'; |             $application->git_branch = 'main'; | ||||||
|             $application->save(); |             $application->save(); | ||||||
|             $application->refresh(); |             $application->refresh(); | ||||||
|             if (! $application->settings->is_container_label_readonly_enabled) { |             if (isset($useBuildServer)) { | ||||||
|  |                 $application->settings->is_build_server_enabled = $useBuildServer; | ||||||
|  |                 $application->settings->save(); | ||||||
|  |             } | ||||||
|  |             if ($application->settings->is_container_label_readonly_enabled) { | ||||||
|                 $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); |                 $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); | ||||||
|                 $application->save(); |                 $application->save(); | ||||||
|             } |             } | ||||||
| @@ -1095,17 +1170,14 @@ class ApplicationsController extends Controller | |||||||
|             return response()->json(serializeApiResponse([ |             return response()->json(serializeApiResponse([ | ||||||
|                 'uuid' => data_get($application, 'uuid'), |                 'uuid' => data_get($application, 'uuid'), | ||||||
|                 'domains' => data_get($application, 'domains'), |                 'domains' => data_get($application, 'domains'), | ||||||
|             ])); |             ]))->setStatusCode(201); | ||||||
|         } elseif ($type === 'dockerimage') { |         } elseif ($type === 'dockerimage') { | ||||||
|             if (! $request->has('name')) { |  | ||||||
|                 $request->offsetSet('name', 'docker-image-'.new Cuid2); |  | ||||||
|             } |  | ||||||
|             $validationRules = [ |             $validationRules = [ | ||||||
|                 'docker_registry_image_name' => 'string|required', |                 'docker_registry_image_name' => 'string|required', | ||||||
|                 'docker_registry_image_tag' => 'string', |                 'docker_registry_image_tag' => 'string', | ||||||
|                 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required', |                 'ports_exposes' => 'string|regex:/^(\d+)(,\d+)*$/|required', | ||||||
|             ]; |             ]; | ||||||
|             $validationRules = array_merge($validationRules, sharedDataApplications()); |             $validationRules = array_merge(sharedDataApplications(), $validationRules); | ||||||
|             $validator = customApiValidator($request->all(), $validationRules); |             $validator = customApiValidator($request->all(), $validationRules); | ||||||
| 
 | 
 | ||||||
|             if ($validator->fails()) { |             if ($validator->fails()) { | ||||||
| @@ -1114,6 +1186,9 @@ class ApplicationsController extends Controller | |||||||
|                     'errors' => $validator->errors(), |                     'errors' => $validator->errors(), | ||||||
|                 ], 422); |                 ], 422); | ||||||
|             } |             } | ||||||
|  |             if (! $request->has('name')) { | ||||||
|  |                 $request->offsetSet('name', 'docker-image-'.new Cuid2); | ||||||
|  |             } | ||||||
|             $return = $this->validateDataApplications($request, $server); |             $return = $this->validateDataApplications($request, $server); | ||||||
|             if ($return instanceof \Illuminate\Http\JsonResponse) { |             if ($return instanceof \Illuminate\Http\JsonResponse) { | ||||||
|                 return $return; |                 return $return; | ||||||
| @@ -1130,16 +1205,16 @@ class ApplicationsController extends Controller | |||||||
|             $application->destination_id = $destination->id; |             $application->destination_id = $destination->id; | ||||||
|             $application->destination_type = $destination->getMorphClass(); |             $application->destination_type = $destination->getMorphClass(); | ||||||
|             $application->environment_id = $environment->id; |             $application->environment_id = $environment->id; | ||||||
|             if (isset($useBuildServer)) { |  | ||||||
|                 $application->settings->is_build_server_enabled = $useBuildServer; |  | ||||||
|                 $application->settings->save(); |  | ||||||
|             } |  | ||||||
| 
 | 
 | ||||||
|             $application->git_repository = 'coollabsio/coolify'; |             $application->git_repository = 'coollabsio/coolify'; | ||||||
|             $application->git_branch = 'main'; |             $application->git_branch = 'main'; | ||||||
|             $application->save(); |             $application->save(); | ||||||
|             $application->refresh(); |             $application->refresh(); | ||||||
|             if (! $application->settings->is_container_label_readonly_enabled) { |             if (isset($useBuildServer)) { | ||||||
|  |                 $application->settings->is_build_server_enabled = $useBuildServer; | ||||||
|  |                 $application->settings->save(); | ||||||
|  |             } | ||||||
|  |             if ($application->settings->is_container_label_readonly_enabled) { | ||||||
|                 $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); |                 $application->custom_labels = str(implode('|coolify|', generateLabelsApplication($application)))->replace('|coolify|', "\n"); | ||||||
|                 $application->save(); |                 $application->save(); | ||||||
|             } |             } | ||||||
| @@ -1159,9 +1234,9 @@ class ApplicationsController extends Controller | |||||||
|             return response()->json(serializeApiResponse([ |             return response()->json(serializeApiResponse([ | ||||||
|                 'uuid' => data_get($application, 'uuid'), |                 'uuid' => data_get($application, 'uuid'), | ||||||
|                 'domains' => data_get($application, 'domains'), |                 'domains' => data_get($application, 'domains'), | ||||||
|             ])); |             ]))->setStatusCode(201); | ||||||
|         } elseif ($type === 'dockercompose') { |         } elseif ($type === 'dockercompose') { | ||||||
|             $allowedFields = ['project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'instant_deploy', 'docker_compose_raw']; |             $allowedFields = ['project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'type', 'name', 'description', 'instant_deploy', 'docker_compose_raw']; | ||||||
| 
 | 
 | ||||||
|             $extraFields = array_diff(array_keys($request->all()), $allowedFields); |             $extraFields = array_diff(array_keys($request->all()), $allowedFields); | ||||||
|             if ($validator->fails() || ! empty($extraFields)) { |             if ($validator->fails() || ! empty($extraFields)) { | ||||||
| @@ -1183,7 +1258,7 @@ class ApplicationsController extends Controller | |||||||
|             $validationRules = [ |             $validationRules = [ | ||||||
|                 'docker_compose_raw' => 'string|required', |                 'docker_compose_raw' => 'string|required', | ||||||
|             ]; |             ]; | ||||||
|             $validationRules = array_merge($validationRules, sharedDataApplications()); |             $validationRules = array_merge(sharedDataApplications(), $validationRules); | ||||||
|             $validator = customApiValidator($request->all(), $validationRules); |             $validator = customApiValidator($request->all(), $validationRules); | ||||||
| 
 | 
 | ||||||
|             if ($validator->fails()) { |             if ($validator->fails()) { | ||||||
| @@ -1241,7 +1316,7 @@ class ApplicationsController extends Controller | |||||||
|             return response()->json(serializeApiResponse([ |             return response()->json(serializeApiResponse([ | ||||||
|                 'uuid' => data_get($service, 'uuid'), |                 'uuid' => data_get($service, 'uuid'), | ||||||
|                 'domains' => data_get($service, 'domains'), |                 'domains' => data_get($service, 'domains'), | ||||||
|             ])); |             ]))->setStatusCode(201); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return response()->json(['message' => 'Invalid type.'], 400); |         return response()->json(['message' => 'Invalid type.'], 400); | ||||||
| @@ -1551,7 +1626,7 @@ class ApplicationsController extends Controller | |||||||
|             'docker_compose_custom_build_command' => 'string|nullable', |             'docker_compose_custom_build_command' => 'string|nullable', | ||||||
|             'custom_nginx_configuration' => 'string|nullable', |             'custom_nginx_configuration' => 'string|nullable', | ||||||
|         ]; |         ]; | ||||||
|         $validationRules = array_merge($validationRules, sharedDataApplications()); |         $validationRules = array_merge(sharedDataApplications(), $validationRules); | ||||||
|         $validator = customApiValidator($request->all(), $validationRules); |         $validator = customApiValidator($request->all(), $validationRules); | ||||||
| 
 | 
 | ||||||
|         // Validate ports_exposes
 |         // Validate ports_exposes
 | ||||||
| @@ -1668,7 +1743,10 @@ class ApplicationsController extends Controller | |||||||
|         removeUnnecessaryFieldsFromRequest($request); |         removeUnnecessaryFieldsFromRequest($request); | ||||||
| 
 | 
 | ||||||
|         $data = $request->all(); |         $data = $request->all(); | ||||||
|         data_set($data, 'fqdn', $domains); |         if ($request->has('domains') && $server->isProxyShouldRun()) { | ||||||
|  |             data_set($data, 'fqdn', $domains); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         if ($dockerComposeDomainsJson->count() > 0) { |         if ($dockerComposeDomainsJson->count() > 0) { | ||||||
|             data_set($data, 'docker_compose_domains', json_encode($dockerComposeDomainsJson)); |             data_set($data, 'docker_compose_domains', json_encode($dockerComposeDomainsJson)); | ||||||
|         } |         } | ||||||
| @@ -1893,8 +1971,9 @@ class ApplicationsController extends Controller | |||||||
|         $is_preview = $request->is_preview ?? false; |         $is_preview = $request->is_preview ?? false; | ||||||
|         $is_build_time = $request->is_build_time ?? false; |         $is_build_time = $request->is_build_time ?? false; | ||||||
|         $is_literal = $request->is_literal ?? false; |         $is_literal = $request->is_literal ?? false; | ||||||
|  |         $key = str($request->key)->trim()->replace(' ', '_')->value; | ||||||
|         if ($is_preview) { |         if ($is_preview) { | ||||||
|             $env = $application->environment_variables_preview->where('key', $request->key)->first(); |             $env = $application->environment_variables_preview->where('key', $key)->first(); | ||||||
|             if ($env) { |             if ($env) { | ||||||
|                 $env->value = $request->value; |                 $env->value = $request->value; | ||||||
|                 if ($env->is_build_time != $is_build_time) { |                 if ($env->is_build_time != $is_build_time) { | ||||||
| @@ -1921,7 +2000,7 @@ class ApplicationsController extends Controller | |||||||
|                 ], 404); |                 ], 404); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             $env = $application->environment_variables->where('key', $request->key)->first(); |             $env = $application->environment_variables->where('key', $key)->first(); | ||||||
|             if ($env) { |             if ($env) { | ||||||
|                 $env->value = $request->value; |                 $env->value = $request->value; | ||||||
|                 if ($env->is_build_time != $is_build_time) { |                 if ($env->is_build_time != $is_build_time) { | ||||||
| @@ -2064,6 +2143,7 @@ class ApplicationsController extends Controller | |||||||
|         $bulk_data = collect($bulk_data)->map(function ($item) { |         $bulk_data = collect($bulk_data)->map(function ($item) { | ||||||
|             return collect($item)->only(['key', 'value', 'is_preview', 'is_build_time', 'is_literal']); |             return collect($item)->only(['key', 'value', 'is_preview', 'is_build_time', 'is_literal']); | ||||||
|         }); |         }); | ||||||
|  |         $returnedEnvs = collect(); | ||||||
|         foreach ($bulk_data as $item) { |         foreach ($bulk_data as $item) { | ||||||
|             $validator = customApiValidator($item, [ |             $validator = customApiValidator($item, [ | ||||||
|                 'key' => 'string|required', |                 'key' => 'string|required', | ||||||
| @@ -2085,8 +2165,9 @@ class ApplicationsController extends Controller | |||||||
|             $is_literal = $item->get('is_literal') ?? false; |             $is_literal = $item->get('is_literal') ?? false; | ||||||
|             $is_multi_line = $item->get('is_multiline') ?? false; |             $is_multi_line = $item->get('is_multiline') ?? false; | ||||||
|             $is_shown_once = $item->get('is_shown_once') ?? false; |             $is_shown_once = $item->get('is_shown_once') ?? false; | ||||||
|  |             $key = str($item->get('key'))->trim()->replace(' ', '_')->value; | ||||||
|             if ($is_preview) { |             if ($is_preview) { | ||||||
|                 $env = $application->environment_variables_preview->where('key', $item->get('key'))->first(); |                 $env = $application->environment_variables_preview->where('key', $key)->first(); | ||||||
|                 if ($env) { |                 if ($env) { | ||||||
|                     $env->value = $item->get('value'); |                     $env->value = $item->get('value'); | ||||||
|                     if ($env->is_build_time != $is_build_time) { |                     if ($env->is_build_time != $is_build_time) { | ||||||
| @@ -2111,10 +2192,12 @@ class ApplicationsController extends Controller | |||||||
|                         'is_literal' => $is_literal, |                         'is_literal' => $is_literal, | ||||||
|                         'is_multiline' => $is_multi_line, |                         'is_multiline' => $is_multi_line, | ||||||
|                         'is_shown_once' => $is_shown_once, |                         'is_shown_once' => $is_shown_once, | ||||||
|  |                         'resourceable_type' => get_class($application), | ||||||
|  |                         'resourceable_id' => $application->id, | ||||||
|                     ]); |                     ]); | ||||||
|                 } |                 } | ||||||
|             } else { |             } else { | ||||||
|                 $env = $application->environment_variables->where('key', $item->get('key'))->first(); |                 $env = $application->environment_variables->where('key', $key)->first(); | ||||||
|                 if ($env) { |                 if ($env) { | ||||||
|                     $env->value = $item->get('value'); |                     $env->value = $item->get('value'); | ||||||
|                     if ($env->is_build_time != $is_build_time) { |                     if ($env->is_build_time != $is_build_time) { | ||||||
| @@ -2139,12 +2222,15 @@ class ApplicationsController extends Controller | |||||||
|                         'is_literal' => $is_literal, |                         'is_literal' => $is_literal, | ||||||
|                         'is_multiline' => $is_multi_line, |                         'is_multiline' => $is_multi_line, | ||||||
|                         'is_shown_once' => $is_shown_once, |                         'is_shown_once' => $is_shown_once, | ||||||
|  |                         'resourceable_type' => get_class($application), | ||||||
|  |                         'resourceable_id' => $application->id, | ||||||
|                     ]); |                     ]); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             $returnedEnvs->push($this->removeSensitiveData($env)); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         return response()->json($this->removeSensitiveData($env))->setStatusCode(201); |         return response()->json($returnedEnvs)->setStatusCode(201); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     #[OA\Post(
 |     #[OA\Post(
 | ||||||
| @@ -2257,8 +2343,10 @@ class ApplicationsController extends Controller | |||||||
|             ], 422); |             ], 422); | ||||||
|         } |         } | ||||||
|         $is_preview = $request->is_preview ?? false; |         $is_preview = $request->is_preview ?? false; | ||||||
|  |         $key = str($request->key)->trim()->replace(' ', '_')->value; | ||||||
|  | 
 | ||||||
|         if ($is_preview) { |         if ($is_preview) { | ||||||
|             $env = $application->environment_variables_preview->where('key', $request->key)->first(); |             $env = $application->environment_variables_preview->where('key', $key)->first(); | ||||||
|             if ($env) { |             if ($env) { | ||||||
|                 return response()->json([ |                 return response()->json([ | ||||||
|                     'message' => 'Environment variable already exists. Use PATCH request to update it.', |                     'message' => 'Environment variable already exists. Use PATCH request to update it.', | ||||||
| @@ -2272,6 +2360,8 @@ class ApplicationsController extends Controller | |||||||
|                     'is_literal' => $request->is_literal ?? false, |                     'is_literal' => $request->is_literal ?? false, | ||||||
|                     'is_multiline' => $request->is_multiline ?? false, |                     'is_multiline' => $request->is_multiline ?? false, | ||||||
|                     'is_shown_once' => $request->is_shown_once ?? false, |                     'is_shown_once' => $request->is_shown_once ?? false, | ||||||
|  |                     'resourceable_type' => get_class($application), | ||||||
|  |                     'resourceable_id' => $application->id, | ||||||
|                 ]); |                 ]); | ||||||
| 
 | 
 | ||||||
|                 return response()->json([ |                 return response()->json([ | ||||||
| @@ -2279,7 +2369,7 @@ class ApplicationsController extends Controller | |||||||
|                 ])->setStatusCode(201); |                 ])->setStatusCode(201); | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             $env = $application->environment_variables->where('key', $request->key)->first(); |             $env = $application->environment_variables->where('key', $key)->first(); | ||||||
|             if ($env) { |             if ($env) { | ||||||
|                 return response()->json([ |                 return response()->json([ | ||||||
|                     'message' => 'Environment variable already exists. Use PATCH request to update it.', |                     'message' => 'Environment variable already exists. Use PATCH request to update it.', | ||||||
| @@ -2293,6 +2383,8 @@ class ApplicationsController extends Controller | |||||||
|                     'is_literal' => $request->is_literal ?? false, |                     'is_literal' => $request->is_literal ?? false, | ||||||
|                     'is_multiline' => $request->is_multiline ?? false, |                     'is_multiline' => $request->is_multiline ?? false, | ||||||
|                     'is_shown_once' => $request->is_shown_once ?? false, |                     'is_shown_once' => $request->is_shown_once ?? false, | ||||||
|  |                     'resourceable_type' => get_class($application), | ||||||
|  |                     'resourceable_id' => $application->id, | ||||||
|                 ]); |                 ]); | ||||||
| 
 | 
 | ||||||
|                 return response()->json([ |                 return response()->json([ | ||||||
| @@ -2380,7 +2472,10 @@ class ApplicationsController extends Controller | |||||||
|                 'message' => 'Application not found.', |                 'message' => 'Application not found.', | ||||||
|             ], 404); |             ], 404); | ||||||
|         } |         } | ||||||
|         $found_env = EnvironmentVariable::where('uuid', $request->env_uuid)->where('application_id', $application->id)->first(); |         $found_env = EnvironmentVariable::where('uuid', $request->env_uuid) | ||||||
|  |             ->where('resourceable_type', Application::class) | ||||||
|  |             ->where('resourceable_id', $application->id) | ||||||
|  |             ->first(); | ||||||
|         if (! $found_env) { |         if (! $found_env) { | ||||||
|             return response()->json([ |             return response()->json([ | ||||||
|                 'message' => 'Environment variable not found.', |                 'message' => 'Environment variable not found.', | ||||||
|   | |||||||
| @@ -523,11 +523,12 @@ class DatabasesController extends Controller | |||||||
|                 mediaType: 'application/json', |                 mediaType: 'application/json', | ||||||
|                 schema: new OA\Schema( |                 schema: new OA\Schema( | ||||||
|                     type: 'object', |                     type: 'object', | ||||||
|                     required: ['server_uuid', 'project_uuid', 'environment_name'], |                     required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], | ||||||
|                     properties: [ |                     properties: [ | ||||||
|                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], |                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], | ||||||
|                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], |                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], | ||||||
|                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], |                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|  |                         'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|                         'postgres_user' => ['type' => 'string', 'description' => 'PostgreSQL user'], |                         'postgres_user' => ['type' => 'string', 'description' => 'PostgreSQL user'], | ||||||
|                         'postgres_password' => ['type' => 'string', 'description' => 'PostgreSQL password'], |                         'postgres_password' => ['type' => 'string', 'description' => 'PostgreSQL password'], | ||||||
|                         'postgres_db' => ['type' => 'string', 'description' => 'PostgreSQL database'], |                         'postgres_db' => ['type' => 'string', 'description' => 'PostgreSQL database'], | ||||||
| @@ -589,11 +590,12 @@ class DatabasesController extends Controller | |||||||
|                 mediaType: 'application/json', |                 mediaType: 'application/json', | ||||||
|                 schema: new OA\Schema( |                 schema: new OA\Schema( | ||||||
|                     type: 'object', |                     type: 'object', | ||||||
|                     required: ['server_uuid', 'project_uuid', 'environment_name'], |                     required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], | ||||||
|                     properties: [ |                     properties: [ | ||||||
|                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], |                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], | ||||||
|                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], |                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], | ||||||
|                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], |                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|  |                         'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|                         'destination_uuid' => ['type' => 'string',  'description' => 'UUID of the destination if the server has multiple destinations'], |                         'destination_uuid' => ['type' => 'string',  'description' => 'UUID of the destination if the server has multiple destinations'], | ||||||
|                         'clickhouse_admin_user' => ['type' => 'string', 'description' => 'Clickhouse admin user'], |                         'clickhouse_admin_user' => ['type' => 'string', 'description' => 'Clickhouse admin user'], | ||||||
|                         'clickhouse_admin_password' => ['type' => 'string', 'description' => 'Clickhouse admin password'], |                         'clickhouse_admin_password' => ['type' => 'string', 'description' => 'Clickhouse admin password'], | ||||||
| @@ -651,11 +653,12 @@ class DatabasesController extends Controller | |||||||
|                 mediaType: 'application/json', |                 mediaType: 'application/json', | ||||||
|                 schema: new OA\Schema( |                 schema: new OA\Schema( | ||||||
|                     type: 'object', |                     type: 'object', | ||||||
|                     required: ['server_uuid', 'project_uuid', 'environment_name'], |                     required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], | ||||||
|                     properties: [ |                     properties: [ | ||||||
|                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], |                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], | ||||||
|                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], |                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], | ||||||
|                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], |                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|  |                         'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|                         'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], |                         'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], | ||||||
|                         'dragonfly_password' => ['type' => 'string', 'description' => 'DragonFly password'], |                         'dragonfly_password' => ['type' => 'string', 'description' => 'DragonFly password'], | ||||||
|                         'name' => ['type' => 'string', 'description' => 'Name of the database'], |                         'name' => ['type' => 'string', 'description' => 'Name of the database'], | ||||||
| @@ -712,11 +715,12 @@ class DatabasesController extends Controller | |||||||
|                 mediaType: 'application/json', |                 mediaType: 'application/json', | ||||||
|                 schema: new OA\Schema( |                 schema: new OA\Schema( | ||||||
|                     type: 'object', |                     type: 'object', | ||||||
|                     required: ['server_uuid', 'project_uuid', 'environment_name'], |                     required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], | ||||||
|                     properties: [ |                     properties: [ | ||||||
|                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], |                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], | ||||||
|                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], |                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], | ||||||
|                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], |                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|  |                         'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|                         'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], |                         'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], | ||||||
|                         'redis_password' => ['type' => 'string', 'description' => 'Redis password'], |                         'redis_password' => ['type' => 'string', 'description' => 'Redis password'], | ||||||
|                         'redis_conf' => ['type' => 'string', 'description' => 'Redis conf'], |                         'redis_conf' => ['type' => 'string', 'description' => 'Redis conf'], | ||||||
| @@ -774,11 +778,12 @@ class DatabasesController extends Controller | |||||||
|                 mediaType: 'application/json', |                 mediaType: 'application/json', | ||||||
|                 schema: new OA\Schema( |                 schema: new OA\Schema( | ||||||
|                     type: 'object', |                     type: 'object', | ||||||
|                     required: ['server_uuid', 'project_uuid', 'environment_name'], |                     required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], | ||||||
|                     properties: [ |                     properties: [ | ||||||
|                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], |                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], | ||||||
|                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], |                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], | ||||||
|                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], |                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|  |                         'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|                         'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], |                         'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], | ||||||
|                         'keydb_password' => ['type' => 'string', 'description' => 'KeyDB password'], |                         'keydb_password' => ['type' => 'string', 'description' => 'KeyDB password'], | ||||||
|                         'keydb_conf' => ['type' => 'string', 'description' => 'KeyDB conf'], |                         'keydb_conf' => ['type' => 'string', 'description' => 'KeyDB conf'], | ||||||
| @@ -836,11 +841,12 @@ class DatabasesController extends Controller | |||||||
|                 mediaType: 'application/json', |                 mediaType: 'application/json', | ||||||
|                 schema: new OA\Schema( |                 schema: new OA\Schema( | ||||||
|                     type: 'object', |                     type: 'object', | ||||||
|                     required: ['server_uuid', 'project_uuid', 'environment_name'], |                     required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], | ||||||
|                     properties: [ |                     properties: [ | ||||||
|                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], |                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], | ||||||
|                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], |                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], | ||||||
|                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], |                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|  |                         'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|                         'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], |                         'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], | ||||||
|                         'mariadb_conf' => ['type' => 'string', 'description' => 'MariaDB conf'], |                         'mariadb_conf' => ['type' => 'string', 'description' => 'MariaDB conf'], | ||||||
|                         'mariadb_root_password' => ['type' => 'string', 'description' => 'MariaDB root password'], |                         'mariadb_root_password' => ['type' => 'string', 'description' => 'MariaDB root password'], | ||||||
| @@ -901,11 +907,12 @@ class DatabasesController extends Controller | |||||||
|                 mediaType: 'application/json', |                 mediaType: 'application/json', | ||||||
|                 schema: new OA\Schema( |                 schema: new OA\Schema( | ||||||
|                     type: 'object', |                     type: 'object', | ||||||
|                     required: ['server_uuid', 'project_uuid', 'environment_name'], |                     required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], | ||||||
|                     properties: [ |                     properties: [ | ||||||
|                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], |                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], | ||||||
|                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], |                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], | ||||||
|                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], |                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|  |                         'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|                         'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], |                         'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], | ||||||
|                         'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'], |                         'mysql_root_password' => ['type' => 'string', 'description' => 'MySQL root password'], | ||||||
|                         'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'], |                         'mysql_password' => ['type' => 'string', 'description' => 'MySQL password'], | ||||||
| @@ -966,11 +973,12 @@ class DatabasesController extends Controller | |||||||
|                 mediaType: 'application/json', |                 mediaType: 'application/json', | ||||||
|                 schema: new OA\Schema( |                 schema: new OA\Schema( | ||||||
|                     type: 'object', |                     type: 'object', | ||||||
|                     required: ['server_uuid', 'project_uuid', 'environment_name'], |                     required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid'], | ||||||
|                     properties: [ |                     properties: [ | ||||||
|                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], |                         'server_uuid' => ['type' => 'string', 'description' => 'UUID of the server'], | ||||||
|                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], |                         'project_uuid' => ['type' => 'string', 'description' => 'UUID of the project'], | ||||||
|                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment'], |                         'environment_name' => ['type' => 'string', 'description' => 'Name of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|  |                         'environment_uuid' => ['type' => 'string', 'description' => 'UUID of the environment. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|                         'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], |                         'destination_uuid' => ['type' => 'string', 'description' => 'UUID of the destination if the server has multiple destinations'], | ||||||
|                         'mongo_conf' => ['type' => 'string', 'description' => 'MongoDB conf'], |                         'mongo_conf' => ['type' => 'string', 'description' => 'MongoDB conf'], | ||||||
|                         'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'MongoDB initdb root username'], |                         'mongo_initdb_root_username' => ['type' => 'string', 'description' => 'MongoDB initdb root username'], | ||||||
| @@ -1013,7 +1021,7 @@ class DatabasesController extends Controller | |||||||
| 
 | 
 | ||||||
|     public function create_database(Request $request, NewDatabaseTypes $type) |     public function create_database(Request $request, NewDatabaseTypes $type) | ||||||
|     { |     { | ||||||
|         $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; |         $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf', 'clickhouse_admin_user', 'clickhouse_admin_password', 'dragonfly_password', 'redis_password', 'redis_conf', 'keydb_password', 'keydb_conf', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; | ||||||
| 
 | 
 | ||||||
|         $teamId = getTeamIdFromToken(); |         $teamId = getTeamIdFromToken(); | ||||||
|         if (is_null($teamId)) { |         if (is_null($teamId)) { | ||||||
| @@ -1039,6 +1047,11 @@ class DatabasesController extends Controller | |||||||
|                 'errors' => $errors, |                 'errors' => $errors, | ||||||
|             ], 422); |             ], 422); | ||||||
|         } |         } | ||||||
|  |         $environmentUuid = $request->environment_uuid; | ||||||
|  |         $environmentName = $request->environment_name; | ||||||
|  |         if (blank($environmentUuid) && blank($environmentName)) { | ||||||
|  |             return response()->json(['message' => 'You need to provide at least one of environment_name or environment_uuid.'], 422); | ||||||
|  |         } | ||||||
|         $serverUuid = $request->server_uuid; |         $serverUuid = $request->server_uuid; | ||||||
|         $instantDeploy = $request->instant_deploy ?? false; |         $instantDeploy = $request->instant_deploy ?? false; | ||||||
|         if ($request->is_public && ! $request->public_port) { |         if ($request->is_public && ! $request->public_port) { | ||||||
| @@ -1048,9 +1061,12 @@ class DatabasesController extends Controller | |||||||
|         if (! $project) { |         if (! $project) { | ||||||
|             return response()->json(['message' => 'Project not found.'], 404); |             return response()->json(['message' => 'Project not found.'], 404); | ||||||
|         } |         } | ||||||
|         $environment = $project->environments()->where('name', $request->environment_name)->first(); |         $environment = $project->environments()->where('name', $environmentName)->first(); | ||||||
|         if (! $environment) { |         if (! $environment) { | ||||||
|             return response()->json(['message' => 'Environment not found.'], 404); |             $environment = $project->environments()->where('uuid', $environmentUuid)->first(); | ||||||
|  |         } | ||||||
|  |         if (! $environment) { | ||||||
|  |             return response()->json(['message' => 'You need to provide a valid environment_name or environment_uuid.'], 422); | ||||||
|         } |         } | ||||||
|         $server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first(); |         $server = Server::whereTeamId($teamId)->whereUuid($serverUuid)->first(); | ||||||
|         if (! $server) { |         if (! $server) { | ||||||
| @@ -1074,7 +1090,8 @@ class DatabasesController extends Controller | |||||||
|             'description' => 'string|nullable', |             'description' => 'string|nullable', | ||||||
|             'image' => 'string', |             'image' => 'string', | ||||||
|             'project_uuid' => 'string|required', |             'project_uuid' => 'string|required', | ||||||
|             'environment_name' => 'string|required', |             'environment_name' => 'string|nullable', | ||||||
|  |             'environment_uuid' => 'string|nullable', | ||||||
|             'server_uuid' => 'string|required', |             'server_uuid' => 'string|required', | ||||||
|             'destination_uuid' => 'string', |             'destination_uuid' => 'string', | ||||||
|             'is_public' => 'boolean', |             'is_public' => 'boolean', | ||||||
| @@ -1105,7 +1122,7 @@ class DatabasesController extends Controller | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|         if ($type === NewDatabaseTypes::POSTGRESQL) { |         if ($type === NewDatabaseTypes::POSTGRESQL) { | ||||||
|             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf']; |             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'postgres_user', 'postgres_password', 'postgres_db', 'postgres_initdb_args', 'postgres_host_auth_method', 'postgres_conf']; | ||||||
|             $validator = customApiValidator($request->all(), [ |             $validator = customApiValidator($request->all(), [ | ||||||
|                 'postgres_user' => 'string', |                 'postgres_user' => 'string', | ||||||
|                 'postgres_password' => 'string', |                 'postgres_password' => 'string', | ||||||
| @@ -1164,7 +1181,7 @@ class DatabasesController extends Controller | |||||||
| 
 | 
 | ||||||
|             return response()->json(serializeApiResponse($payload))->setStatusCode(201); |             return response()->json(serializeApiResponse($payload))->setStatusCode(201); | ||||||
|         } elseif ($type === NewDatabaseTypes::MARIADB) { |         } elseif ($type === NewDatabaseTypes::MARIADB) { | ||||||
|             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database']; |             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mariadb_conf', 'mariadb_root_password', 'mariadb_user', 'mariadb_password', 'mariadb_database']; | ||||||
|             $validator = customApiValidator($request->all(), [ |             $validator = customApiValidator($request->all(), [ | ||||||
|                 'clickhouse_admin_user' => 'string', |                 'clickhouse_admin_user' => 'string', | ||||||
|                 'clickhouse_admin_password' => 'string', |                 'clickhouse_admin_password' => 'string', | ||||||
| @@ -1220,7 +1237,7 @@ class DatabasesController extends Controller | |||||||
| 
 | 
 | ||||||
|             return response()->json(serializeApiResponse($payload))->setStatusCode(201); |             return response()->json(serializeApiResponse($payload))->setStatusCode(201); | ||||||
|         } elseif ($type === NewDatabaseTypes::MYSQL) { |         } elseif ($type === NewDatabaseTypes::MYSQL) { | ||||||
|             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; |             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mysql_root_password', 'mysql_password', 'mysql_user', 'mysql_database', 'mysql_conf']; | ||||||
|             $validator = customApiValidator($request->all(), [ |             $validator = customApiValidator($request->all(), [ | ||||||
|                 'mysql_root_password' => 'string', |                 'mysql_root_password' => 'string', | ||||||
|                 'mysql_password' => 'string', |                 'mysql_password' => 'string', | ||||||
| @@ -1279,7 +1296,7 @@ class DatabasesController extends Controller | |||||||
| 
 | 
 | ||||||
|             return response()->json(serializeApiResponse($payload))->setStatusCode(201); |             return response()->json(serializeApiResponse($payload))->setStatusCode(201); | ||||||
|         } elseif ($type === NewDatabaseTypes::REDIS) { |         } elseif ($type === NewDatabaseTypes::REDIS) { | ||||||
|             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'redis_password', 'redis_conf']; |             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'redis_password', 'redis_conf']; | ||||||
|             $validator = customApiValidator($request->all(), [ |             $validator = customApiValidator($request->all(), [ | ||||||
|                 'redis_password' => 'string', |                 'redis_password' => 'string', | ||||||
|                 'redis_conf' => 'string', |                 'redis_conf' => 'string', | ||||||
| @@ -1335,7 +1352,7 @@ class DatabasesController extends Controller | |||||||
| 
 | 
 | ||||||
|             return response()->json(serializeApiResponse($payload))->setStatusCode(201); |             return response()->json(serializeApiResponse($payload))->setStatusCode(201); | ||||||
|         } elseif ($type === NewDatabaseTypes::DRAGONFLY) { |         } elseif ($type === NewDatabaseTypes::DRAGONFLY) { | ||||||
|             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares',  'dragonfly_password']; |             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares',  'dragonfly_password']; | ||||||
|             $validator = customApiValidator($request->all(), [ |             $validator = customApiValidator($request->all(), [ | ||||||
|                 'dragonfly_password' => 'string', |                 'dragonfly_password' => 'string', | ||||||
|             ]); |             ]); | ||||||
| @@ -1365,7 +1382,7 @@ class DatabasesController extends Controller | |||||||
|                 'uuid' => $database->uuid, |                 'uuid' => $database->uuid, | ||||||
|             ]))->setStatusCode(201); |             ]))->setStatusCode(201); | ||||||
|         } elseif ($type === NewDatabaseTypes::KEYDB) { |         } elseif ($type === NewDatabaseTypes::KEYDB) { | ||||||
|             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'keydb_password', 'keydb_conf']; |             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'keydb_password', 'keydb_conf']; | ||||||
|             $validator = customApiValidator($request->all(), [ |             $validator = customApiValidator($request->all(), [ | ||||||
|                 'keydb_password' => 'string', |                 'keydb_password' => 'string', | ||||||
|                 'keydb_conf' => 'string', |                 'keydb_conf' => 'string', | ||||||
| @@ -1421,7 +1438,7 @@ class DatabasesController extends Controller | |||||||
| 
 | 
 | ||||||
|             return response()->json(serializeApiResponse($payload))->setStatusCode(201); |             return response()->json(serializeApiResponse($payload))->setStatusCode(201); | ||||||
|         } elseif ($type === NewDatabaseTypes::CLICKHOUSE) { |         } elseif ($type === NewDatabaseTypes::CLICKHOUSE) { | ||||||
|             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares',  'clickhouse_admin_user', 'clickhouse_admin_password']; |             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares',  'clickhouse_admin_user', 'clickhouse_admin_password']; | ||||||
|             $validator = customApiValidator($request->all(), [ |             $validator = customApiValidator($request->all(), [ | ||||||
|                 'clickhouse_admin_user' => 'string', |                 'clickhouse_admin_user' => 'string', | ||||||
|                 'clickhouse_admin_password' => 'string', |                 'clickhouse_admin_password' => 'string', | ||||||
| @@ -1457,7 +1474,7 @@ class DatabasesController extends Controller | |||||||
| 
 | 
 | ||||||
|             return response()->json(serializeApiResponse($payload))->setStatusCode(201); |             return response()->json(serializeApiResponse($payload))->setStatusCode(201); | ||||||
|         } elseif ($type === NewDatabaseTypes::MONGODB) { |         } elseif ($type === NewDatabaseTypes::MONGODB) { | ||||||
|             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database']; |             $allowedFields = ['name', 'description', 'image', 'public_port', 'is_public', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'mongo_conf', 'mongo_initdb_root_username', 'mongo_initdb_root_password', 'mongo_initdb_database']; | ||||||
|             $validator = customApiValidator($request->all(), [ |             $validator = customApiValidator($request->all(), [ | ||||||
|                 'mongo_conf' => 'string', |                 'mongo_conf' => 'string', | ||||||
|                 'mongo_initdb_root_username' => 'string', |                 'mongo_initdb_root_username' => 'string', | ||||||
|   | |||||||
| @@ -90,11 +90,13 @@ class ProjectController extends Controller | |||||||
|         if (is_null($teamId)) { |         if (is_null($teamId)) { | ||||||
|             return invalidTokenResponse(); |             return invalidTokenResponse(); | ||||||
|         } |         } | ||||||
|         $project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first()->load(['environments']); |         $project = Project::whereTeamId($teamId)->whereUuid(request()->uuid)->first(); | ||||||
|         if (! $project) { |         if (! $project) { | ||||||
|             return response()->json(['message' => 'Project not found.'], 404); |             return response()->json(['message' => 'Project not found.'], 404); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  |         $project->load(['environments']); | ||||||
|  | 
 | ||||||
|         return response()->json( |         return response()->json( | ||||||
|             serializeApiResponse($project), |             serializeApiResponse($project), | ||||||
|         ); |         ); | ||||||
| @@ -102,16 +104,16 @@ class ProjectController extends Controller | |||||||
| 
 | 
 | ||||||
|     #[OA\Get(
 |     #[OA\Get(
 | ||||||
|         summary: 'Environment', |         summary: 'Environment', | ||||||
|         description: 'Get environment by name.', |         description: 'Get environment by name or UUID.', | ||||||
|         path: '/projects/{uuid}/{environment_name}', |         path: '/projects/{uuid}/{environment_name_or_uuid}', | ||||||
|         operationId: 'get-environment-by-name', |         operationId: 'get-environment-by-name-or-uuid', | ||||||
|         security: [ |         security: [ | ||||||
|             ['bearerAuth' => []], |             ['bearerAuth' => []], | ||||||
|         ], |         ], | ||||||
|         tags: ['Projects'], |         tags: ['Projects'], | ||||||
|         parameters: [ |         parameters: [ | ||||||
|             new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'string')), |             new OA\Parameter(name: 'uuid', in: 'path', required: true, description: 'Project UUID', schema: new OA\Schema(type: 'string')), | ||||||
|             new OA\Parameter(name: 'environment_name', in: 'path', required: true, description: 'Environment name', schema: new OA\Schema(type: 'string')), |             new OA\Parameter(name: 'environment_name_or_uuid', in: 'path', required: true, description: 'Environment name or UUID', schema: new OA\Schema(type: 'string')), | ||||||
|         ], |         ], | ||||||
|         responses: [ |         responses: [ | ||||||
|             new OA\Response( |             new OA\Response( | ||||||
| @@ -141,14 +143,17 @@ class ProjectController extends Controller | |||||||
|         if (! $request->uuid) { |         if (! $request->uuid) { | ||||||
|             return response()->json(['message' => 'UUID is required.'], 422); |             return response()->json(['message' => 'UUID is required.'], 422); | ||||||
|         } |         } | ||||||
|         if (! $request->environment_name) { |         if (! $request->environment_name_or_uuid) { | ||||||
|             return response()->json(['message' => 'Environment name is required.'], 422); |             return response()->json(['message' => 'Environment name or UUID is required.'], 422); | ||||||
|         } |         } | ||||||
|         $project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first(); |         $project = Project::whereTeamId($teamId)->whereUuid($request->uuid)->first(); | ||||||
|         if (! $project) { |         if (! $project) { | ||||||
|             return response()->json(['message' => 'Project not found.'], 404); |             return response()->json(['message' => 'Project not found.'], 404); | ||||||
|         } |         } | ||||||
|         $environment = $project->environments()->whereName($request->environment_name)->first(); |         $environment = $project->environments()->whereName($request->environment_name_or_uuid)->first(); | ||||||
|  |         if (! $environment) { | ||||||
|  |             $environment = $project->environments()->whereUuid($request->environment_name_or_uuid)->first(); | ||||||
|  |         } | ||||||
|         if (! $environment) { |         if (! $environment) { | ||||||
|             return response()->json(['message' => 'Environment not found.'], 404); |             return response()->json(['message' => 'Environment not found.'], 404); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -20,6 +20,9 @@ class ServicesController extends Controller | |||||||
|     { |     { | ||||||
|         $service->makeHidden([ |         $service->makeHidden([ | ||||||
|             'id', |             'id', | ||||||
|  |             'resourceable', | ||||||
|  |             'resourceable_id', | ||||||
|  |             'resourceable_type', | ||||||
|         ]); |         ]); | ||||||
|         if (request()->attributes->get('can_read_sensitive', false) === false) { |         if (request()->attributes->get('can_read_sensitive', false) === false) { | ||||||
|             $service->makeHidden([ |             $service->makeHidden([ | ||||||
| @@ -99,7 +102,7 @@ class ServicesController extends Controller | |||||||
|                 mediaType: 'application/json', |                 mediaType: 'application/json', | ||||||
|                 schema: new OA\Schema( |                 schema: new OA\Schema( | ||||||
|                     type: 'object', |                     type: 'object', | ||||||
|                     required: ['server_uuid', 'project_uuid', 'environment_name', 'type'], |                     required: ['server_uuid', 'project_uuid', 'environment_name', 'environment_uuid', 'type'], | ||||||
|                     properties: [ |                     properties: [ | ||||||
|                         'type' => [ |                         'type' => [ | ||||||
|                             'description' => 'The one-click service type', |                             'description' => 'The one-click service type', | ||||||
| @@ -196,7 +199,8 @@ class ServicesController extends Controller | |||||||
|                         'name' => ['type' => 'string', 'maxLength' => 255, 'description' => 'Name of the service.'], |                         'name' => ['type' => 'string', 'maxLength' => 255, 'description' => 'Name of the service.'], | ||||||
|                         'description' => ['type' => 'string', 'nullable' => true, 'description' => 'Description of the service.'], |                         'description' => ['type' => 'string', 'nullable' => true, 'description' => 'Description of the service.'], | ||||||
|                         'project_uuid' => ['type' => 'string', 'description' => 'Project UUID.'], |                         'project_uuid' => ['type' => 'string', 'description' => 'Project UUID.'], | ||||||
|                         'environment_name' => ['type' => 'string', 'description' => 'Environment name.'], |                         'environment_name' => ['type' => 'string', 'description' => 'Environment name. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|  |                         'environment_uuid' => ['type' => 'string', 'description' => 'Environment UUID. You need to provide at least one of environment_name or environment_uuid.'], | ||||||
|                         'server_uuid' => ['type' => 'string', 'description' => 'Server UUID.'], |                         'server_uuid' => ['type' => 'string', 'description' => 'Server UUID.'], | ||||||
|                         'destination_uuid' => ['type' => 'string', 'description' => 'Destination UUID. Required if server has multiple destinations.'], |                         'destination_uuid' => ['type' => 'string', 'description' => 'Destination UUID. Required if server has multiple destinations.'], | ||||||
|                         'instant_deploy' => ['type' => 'boolean', 'default' => false, 'description' => 'Start the service immediately after creation.'], |                         'instant_deploy' => ['type' => 'boolean', 'default' => false, 'description' => 'Start the service immediately after creation.'], | ||||||
| @@ -233,7 +237,7 @@ class ServicesController extends Controller | |||||||
|     )] |     )] | ||||||
|     public function create_service(Request $request) |     public function create_service(Request $request) | ||||||
|     { |     { | ||||||
|         $allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'server_uuid', 'destination_uuid', 'instant_deploy']; |         $allowedFields = ['type', 'name', 'description', 'project_uuid', 'environment_name', 'environment_uuid', 'server_uuid', 'destination_uuid', 'instant_deploy']; | ||||||
| 
 | 
 | ||||||
|         $teamId = getTeamIdFromToken(); |         $teamId = getTeamIdFromToken(); | ||||||
|         if (is_null($teamId)) { |         if (is_null($teamId)) { | ||||||
| @@ -247,7 +251,8 @@ class ServicesController extends Controller | |||||||
|         $validator = customApiValidator($request->all(), [ |         $validator = customApiValidator($request->all(), [ | ||||||
|             'type' => 'string|required', |             'type' => 'string|required', | ||||||
|             'project_uuid' => 'string|required', |             'project_uuid' => 'string|required', | ||||||
|             'environment_name' => 'string|required', |             'environment_name' => 'string|nullable', | ||||||
|  |             'environment_uuid' => 'string|nullable', | ||||||
|             'server_uuid' => 'string|required', |             'server_uuid' => 'string|required', | ||||||
|             'destination_uuid' => 'string', |             'destination_uuid' => 'string', | ||||||
|             'name' => 'string|max:255', |             'name' => 'string|max:255', | ||||||
| @@ -269,6 +274,11 @@ class ServicesController extends Controller | |||||||
|                 'errors' => $errors, |                 'errors' => $errors, | ||||||
|             ], 422); |             ], 422); | ||||||
|         } |         } | ||||||
|  |         $environmentUuid = $request->environment_uuid; | ||||||
|  |         $environmentName = $request->environment_name; | ||||||
|  |         if (blank($environmentUuid) && blank($environmentName)) { | ||||||
|  |             return response()->json(['message' => 'You need to provide at least one of environment_name or environment_uuid.'], 422); | ||||||
|  |         } | ||||||
|         $serverUuid = $request->server_uuid; |         $serverUuid = $request->server_uuid; | ||||||
|         $instantDeploy = $request->instant_deploy ?? false; |         $instantDeploy = $request->instant_deploy ?? false; | ||||||
|         if ($request->is_public && ! $request->public_port) { |         if ($request->is_public && ! $request->public_port) { | ||||||
| @@ -278,7 +288,10 @@ class ServicesController extends Controller | |||||||
|         if (! $project) { |         if (! $project) { | ||||||
|             return response()->json(['message' => 'Project not found.'], 404); |             return response()->json(['message' => 'Project not found.'], 404); | ||||||
|         } |         } | ||||||
|         $environment = $project->environments()->where('name', $request->environment_name)->first(); |         $environment = $project->environments()->where('name', $environmentName)->first(); | ||||||
|  |         if (! $environment) { | ||||||
|  |             $environment = $project->environments()->where('uuid', $environmentUuid)->first(); | ||||||
|  |         } | ||||||
|         if (! $environment) { |         if (! $environment) { | ||||||
|             return response()->json(['message' => 'Environment not found.'], 404); |             return response()->json(['message' => 'Environment not found.'], 404); | ||||||
|         } |         } | ||||||
| @@ -333,7 +346,8 @@ class ServicesController extends Controller | |||||||
|                         EnvironmentVariable::create([ |                         EnvironmentVariable::create([ | ||||||
|                             'key' => $key, |                             'key' => $key, | ||||||
|                             'value' => $generatedValue, |                             'value' => $generatedValue, | ||||||
|                             'service_id' => $service->id, |                             'resourceable_id' => $service->id, | ||||||
|  |                             'resourceable_type' => $service->getMorphClass(), | ||||||
|                             'is_build_time' => false, |                             'is_build_time' => false, | ||||||
|                             'is_preview' => false, |                             'is_preview' => false, | ||||||
|                         ]); |                         ]); | ||||||
| @@ -345,7 +359,11 @@ class ServicesController extends Controller | |||||||
|                 } |                 } | ||||||
|                 $domains = $service->applications()->get()->pluck('fqdn')->sort(); |                 $domains = $service->applications()->get()->pluck('fqdn')->sort(); | ||||||
|                 $domains = $domains->map(function ($domain) { |                 $domains = $domains->map(function ($domain) { | ||||||
|                     return str($domain)->beforeLast(':')->value(); |                     if (count(explode(':', $domain)) > 2) { | ||||||
|  |                         return str($domain)->beforeLast(':')->value(); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     return $domain; | ||||||
|                 }); |                 }); | ||||||
| 
 | 
 | ||||||
|                 return response()->json([ |                 return response()->json([ | ||||||
| @@ -673,7 +691,8 @@ class ServicesController extends Controller | |||||||
|             ], 422); |             ], 422); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $env = $service->environment_variables()->where('key', $request->key)->first(); |         $key = str($request->key)->trim()->replace(' ', '_')->value; | ||||||
|  |         $env = $service->environment_variables()->where('key', $key)->first(); | ||||||
|         if (! $env) { |         if (! $env) { | ||||||
|             return response()->json(['message' => 'Environment variable not found.'], 404); |             return response()->json(['message' => 'Environment variable not found.'], 404); | ||||||
|         } |         } | ||||||
| @@ -799,9 +818,9 @@ class ServicesController extends Controller | |||||||
|                     'errors' => $validator->errors(), |                     'errors' => $validator->errors(), | ||||||
|                 ], 422); |                 ], 422); | ||||||
|             } |             } | ||||||
| 
 |             $key = str($item['key'])->trim()->replace(' ', '_')->value; | ||||||
|             $env = $service->environment_variables()->updateOrCreate( |             $env = $service->environment_variables()->updateOrCreate( | ||||||
|                 ['key' => $item['key']], |                 ['key' => $key], | ||||||
|                 $item |                 $item | ||||||
|             ); |             ); | ||||||
| 
 | 
 | ||||||
| @@ -909,7 +928,8 @@ class ServicesController extends Controller | |||||||
|             ], 422); |             ], 422); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $existingEnv = $service->environment_variables()->where('key', $request->key)->first(); |         $key = str($request->key)->trim()->replace(' ', '_')->value; | ||||||
|  |         $existingEnv = $service->environment_variables()->where('key', $key)->first(); | ||||||
|         if ($existingEnv) { |         if ($existingEnv) { | ||||||
|             return response()->json([ |             return response()->json([ | ||||||
|                 'message' => 'Environment variable already exists. Use PATCH request to update it.', |                 'message' => 'Environment variable already exists. Use PATCH request to update it.', | ||||||
| @@ -995,7 +1015,8 @@ class ServicesController extends Controller | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $env = EnvironmentVariable::where('uuid', $request->env_uuid) |         $env = EnvironmentVariable::where('uuid', $request->env_uuid) | ||||||
|             ->where('service_id', $service->id) |             ->where('resourceable_type', Service::class) | ||||||
|  |             ->where('resourceable_id', $service->id) | ||||||
|             ->first(); |             ->first(); | ||||||
| 
 | 
 | ||||||
|         if (! $env) { |         if (! $env) { | ||||||
|   | |||||||
| @@ -39,12 +39,12 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue | |||||||
| { | { | ||||||
|     use Dispatchable, ExecuteRemoteCommand, InteractsWithQueue, Queueable, SerializesModels; |     use Dispatchable, ExecuteRemoteCommand, InteractsWithQueue, Queueable, SerializesModels; | ||||||
| 
 | 
 | ||||||
|  |     public $tries = 1; | ||||||
|  | 
 | ||||||
|     public $timeout = 3600; |     public $timeout = 3600; | ||||||
| 
 | 
 | ||||||
|     public static int $batch_counter = 0; |     public static int $batch_counter = 0; | ||||||
| 
 | 
 | ||||||
|     private int $application_deployment_queue_id; |  | ||||||
| 
 |  | ||||||
|     private bool $newVersionIsHealthy = false; |     private bool $newVersionIsHealthy = false; | ||||||
| 
 | 
 | ||||||
|     private ApplicationDeploymentQueue $application_deployment_queue; |     private ApplicationDeploymentQueue $application_deployment_queue; | ||||||
| @@ -126,6 +126,8 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue | |||||||
| 
 | 
 | ||||||
|     private ?string $nixpacks_plan = null; |     private ?string $nixpacks_plan = null; | ||||||
| 
 | 
 | ||||||
|  |     private Collection $nixpacks_plan_json; | ||||||
|  | 
 | ||||||
|     private ?string $nixpacks_type = null; |     private ?string $nixpacks_type = null; | ||||||
| 
 | 
 | ||||||
|     private string $dockerfile_location = '/Dockerfile'; |     private string $dockerfile_location = '/Dockerfile'; | ||||||
| @@ -164,18 +166,23 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue | |||||||
| 
 | 
 | ||||||
|     private bool $preserveRepository = false; |     private bool $preserveRepository = false; | ||||||
| 
 | 
 | ||||||
|     public $tries = 1; |     public function tags() | ||||||
|  |     { | ||||||
|  |         // Do not remove this one, it needs to properly identify which worker is running the job
 | ||||||
|  |         return ['App\Models\ApplicationDeploymentQueue:'.$this->application_deployment_queue_id]; | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     public function __construct(int $application_deployment_queue_id) |     public function __construct(public int $application_deployment_queue_id) | ||||||
|     { |     { | ||||||
|         $this->onQueue('high'); |         $this->onQueue('high'); | ||||||
| 
 | 
 | ||||||
|         $this->application_deployment_queue = ApplicationDeploymentQueue::find($application_deployment_queue_id); |         $this->application_deployment_queue = ApplicationDeploymentQueue::find($this->application_deployment_queue_id); | ||||||
|  |         $this->nixpacks_plan_json = collect([]); | ||||||
|  | 
 | ||||||
|         $this->application = Application::find($this->application_deployment_queue->application_id); |         $this->application = Application::find($this->application_deployment_queue->application_id); | ||||||
|         $this->build_pack = data_get($this->application, 'build_pack'); |         $this->build_pack = data_get($this->application, 'build_pack'); | ||||||
|         $this->build_args = collect([]); |         $this->build_args = collect([]); | ||||||
| 
 | 
 | ||||||
|         $this->application_deployment_queue_id = $application_deployment_queue_id; |  | ||||||
|         $this->deployment_uuid = $this->application_deployment_queue->deployment_uuid; |         $this->deployment_uuid = $this->application_deployment_queue->deployment_uuid; | ||||||
|         $this->pull_request_id = $this->application_deployment_queue->pull_request_id; |         $this->pull_request_id = $this->application_deployment_queue->pull_request_id; | ||||||
|         $this->commit = $this->application_deployment_queue->commit; |         $this->commit = $this->application_deployment_queue->commit; | ||||||
| @@ -233,15 +240,11 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function tags(): array |  | ||||||
|     { |  | ||||||
|         return ['server:'.gethostname()]; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function handle(): void |     public function handle(): void | ||||||
|     { |     { | ||||||
|         $this->application_deployment_queue->update([ |         $this->application_deployment_queue->update([ | ||||||
|             'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, |             'status' => ApplicationDeploymentStatus::IN_PROGRESS->value, | ||||||
|  |             'horizon_job_worker' => gethostname(), | ||||||
|         ]); |         ]); | ||||||
|         if ($this->server->isFunctional() === false) { |         if ($this->server->isFunctional() === false) { | ||||||
|             $this->application_deployment_queue->addLogEntry('Server is not functional.'); |             $this->application_deployment_queue->addLogEntry('Server is not functional.'); | ||||||
| @@ -1405,7 +1408,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|                 'project_uuid' => data_get($this->application, 'environment.project.uuid'), |                 'project_uuid' => data_get($this->application, 'environment.project.uuid'), | ||||||
|                 'application_uuid' => data_get($this->application, 'uuid'), |                 'application_uuid' => data_get($this->application, 'uuid'), | ||||||
|                 'deployment_uuid' => $deployment_uuid, |                 'deployment_uuid' => $deployment_uuid, | ||||||
|                 'environment_name' => data_get($this->application, 'environment.name'), |                 'environment_uuid' => data_get($this->application, 'environment.uuid'), | ||||||
|             ])); |             ])); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -1545,7 +1548,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue | |||||||
| 
 | 
 | ||||||
|                 // Do any modifications here
 |                 // Do any modifications here
 | ||||||
|                 $this->generate_env_variables(); |                 $this->generate_env_variables(); | ||||||
|                 $merged_envs = $this->env_args->merge(collect(data_get($parsed, 'variables', []))); |                 $merged_envs = collect(data_get($parsed, 'variables', []))->merge($this->env_args); | ||||||
|                 $aptPkgs = data_get($parsed, 'phases.setup.aptPkgs', []); |                 $aptPkgs = data_get($parsed, 'phases.setup.aptPkgs', []); | ||||||
|                 if (count($aptPkgs) === 0) { |                 if (count($aptPkgs) === 0) { | ||||||
|                     $aptPkgs = ['curl', 'wget']; |                     $aptPkgs = ['curl', 'wget']; | ||||||
| @@ -1570,6 +1573,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|                     $this->elixir_finetunes(); |                     $this->elixir_finetunes(); | ||||||
|                 } |                 } | ||||||
|                 $this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT); |                 $this->nixpacks_plan = json_encode($parsed, JSON_PRETTY_PRINT); | ||||||
|  |                 $this->nixpacks_plan_json = collect($parsed); | ||||||
|                 $this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true); |                 $this->application_deployment_queue->addLogEntry("Final Nixpacks plan: {$this->nixpacks_plan}", hidden: true); | ||||||
|                 if ($this->nixpacks_type === 'rust') { |                 if ($this->nixpacks_type === 'rust') { | ||||||
|                     // temporary: disable healthcheck for rust because the start phase does not have curl/wget
 |                     // temporary: disable healthcheck for rust because the start phase does not have curl/wget
 | ||||||
| @@ -1678,7 +1682,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|             $this->application->custom_labels = base64_encode($labels->implode("\n")); |             $this->application->custom_labels = base64_encode($labels->implode("\n")); | ||||||
|             $this->application->save(); |             $this->application->save(); | ||||||
|         } else { |         } else { | ||||||
|             if (! $this->application->settings->is_container_label_readonly_enabled) { |             if ($this->application->settings->is_container_label_readonly_enabled) { | ||||||
|                 $labels = collect(generateLabelsApplication($this->application, $this->preview)); |                 $labels = collect(generateLabelsApplication($this->application, $this->preview)); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
| @@ -1690,7 +1694,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|                 return escapeDollarSign($value); |                 return escapeDollarSign($value); | ||||||
|             }); |             }); | ||||||
|         } |         } | ||||||
|         $labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray(); |         $labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->application->project()->name, $this->application->name, $this->application->environment->name, $this->pull_request_id))->toArray(); | ||||||
| 
 | 
 | ||||||
|         // Check for custom HEALTHCHECK
 |         // Check for custom HEALTHCHECK
 | ||||||
|         if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) { |         if ($this->application->build_pack === 'dockerfile' || $this->application->dockerfile) { | ||||||
| @@ -2278,18 +2282,10 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); | |||||||
| 
 | 
 | ||||||
|     private function generate_build_env_variables() |     private function generate_build_env_variables() | ||||||
|     { |     { | ||||||
|         $this->build_args = collect(["--build-arg SOURCE_COMMIT=\"{$this->commit}\""]); |         $variables = collect($this->nixpacks_plan_json->get('variables')); | ||||||
|         if ($this->pull_request_id === 0) { |         $this->build_args = $variables->map(function ($value, $key) { | ||||||
|             foreach ($this->application->build_environment_variables as $env) { |             return "--build-arg {$key}={$value}"; | ||||||
|                 $value = escapeshellarg($env->real_value); |         }); | ||||||
|                 $this->build_args->push("--build-arg {$env->key}={$value}"); |  | ||||||
|             } |  | ||||||
|         } else { |  | ||||||
|             foreach ($this->application->build_environment_variables_preview as $env) { |  | ||||||
|                 $value = escapeshellarg($env->real_value); |  | ||||||
|                 $this->build_args->push("--build-arg {$env->key}={$value}"); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function add_build_env_variables_to_dockerfile() |     private function add_build_env_variables_to_dockerfile() | ||||||
| @@ -2394,7 +2390,8 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf"); | |||||||
|         queue_next_deployment($this->application); |         queue_next_deployment($this->application); | ||||||
|         // If the deployment is cancelled by the user, don't update the status
 |         // If the deployment is cancelled by the user, don't update the status
 | ||||||
|         if ( |         if ( | ||||||
|             $this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value |             $this->application_deployment_queue->status !== ApplicationDeploymentStatus::CANCELLED_BY_USER->value && | ||||||
|  |             $this->application_deployment_queue->status !== ApplicationDeploymentStatus::FAILED->value | ||||||
|         ) { |         ) { | ||||||
|             $this->application_deployment_queue->update([ |             $this->application_deployment_queue->update([ | ||||||
|                 'status' => $status, |                 'status' => $status, | ||||||
|   | |||||||
| @@ -24,7 +24,7 @@ class CheckAndStartSentinelJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|         $latestVersion = get_latest_sentinel_version(); |         $latestVersion = get_latest_sentinel_version(); | ||||||
| 
 | 
 | ||||||
|         // Check if sentinel is running
 |         // Check if sentinel is running
 | ||||||
|         $sentinelFound = instant_remote_process(['docker inspect coolify-sentinel'], $this->server, false); |         $sentinelFound = instant_remote_process_with_timeout(['docker inspect coolify-sentinel'], $this->server, false, 10); | ||||||
|         $sentinelFoundJson = json_decode($sentinelFound, true); |         $sentinelFoundJson = json_decode($sentinelFound, true); | ||||||
|         $sentinelStatus = data_get($sentinelFoundJson, '0.State.Status', 'exited'); |         $sentinelStatus = data_get($sentinelFoundJson, '0.State.Status', 'exited'); | ||||||
|         if ($sentinelStatus !== 'running') { |         if ($sentinelStatus !== 'running') { | ||||||
| @@ -33,7 +33,7 @@ class CheckAndStartSentinelJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         // If sentinel is running, check if it needs an update
 |         // If sentinel is running, check if it needs an update
 | ||||||
|         $runningVersion = instant_remote_process(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/version"'], $this->server, false); |         $runningVersion = instant_remote_process_with_timeout(['docker exec coolify-sentinel sh -c "curl http://127.0.0.1:8888/api/version"'], $this->server, false); | ||||||
|         if (empty($runningVersion)) { |         if (empty($runningVersion)) { | ||||||
|             $runningVersion = '0.0.0'; |             $runningVersion = '0.0.0'; | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -20,11 +20,11 @@ class CleanupHelperContainersJob implements ShouldBeEncrypted, ShouldBeUnique, S | |||||||
|     public function handle(): void |     public function handle(): void | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $containers = instant_remote_process(['docker container ps --format \'{{json .}}\' | jq -s \'map(select(.Image | contains("ghcr.io/coollabsio/coolify-helper")))\''], $this->server, false); |             $containers = instant_remote_process_with_timeout(['docker container ps --format \'{{json .}}\' | jq -s \'map(select(.Image | contains("ghcr.io/coollabsio/coolify-helper")))\''], $this->server, false); | ||||||
|             $containerIds = collect(json_decode($containers))->pluck('ID'); |             $containerIds = collect(json_decode($containers))->pluck('ID'); | ||||||
|             if ($containerIds->count() > 0) { |             if ($containerIds->count() > 0) { | ||||||
|                 foreach ($containerIds as $containerId) { |                 foreach ($containerIds as $containerId) { | ||||||
|                     instant_remote_process(['docker container rm -f '.$containerId], $this->server, false); |                     instant_remote_process_with_timeout(['docker container rm -f '.$containerId], $this->server, false); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|   | |||||||
| @@ -32,8 +32,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue | |||||||
| 
 | 
 | ||||||
|     public Server $server; |     public Server $server; | ||||||
| 
 | 
 | ||||||
|     public ScheduledDatabaseBackup $backup; |  | ||||||
| 
 |  | ||||||
|     public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database; |     public StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database; | ||||||
| 
 | 
 | ||||||
|     public ?string $container_name = null; |     public ?string $container_name = null; | ||||||
| @@ -58,10 +56,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue | |||||||
| 
 | 
 | ||||||
|     public ?S3Storage $s3 = null; |     public ?S3Storage $s3 = null; | ||||||
| 
 | 
 | ||||||
|     public function __construct($backup) |     public function __construct(public ScheduledDatabaseBackup $backup) | ||||||
|     { |     { | ||||||
|         $this->onQueue('high'); |         $this->onQueue('high'); | ||||||
|         $this->backup = $backup; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function handle(): void |     public function handle(): void | ||||||
| @@ -302,7 +299,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|                         throw new \Exception('Unsupported database type'); |                         throw new \Exception('Unsupported database type'); | ||||||
|                     } |                     } | ||||||
|                     $size = $this->calculate_size(); |                     $size = $this->calculate_size(); | ||||||
|                     $this->remove_old_backups(); |  | ||||||
|                     if ($this->backup->save_s3) { |                     if ($this->backup->save_s3) { | ||||||
|                         $this->upload_to_s3(); |                         $this->upload_to_s3(); | ||||||
|                     } |                     } | ||||||
| @@ -326,6 +322,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|                     $this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output, $database)); |                     $this->team?->notify(new BackupFailed($this->backup, $this->database, $this->backup_output, $database)); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |             if ($this->backup_log && $this->backup_log->status === 'success') { | ||||||
|  |                 removeOldBackups($this->backup); | ||||||
|  |             } | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             throw $e; |             throw $e; | ||||||
|         } finally { |         } finally { | ||||||
| @@ -460,19 +459,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|         return instant_remote_process(["du -b $this->backup_location | cut -f1"], $this->server, false); |         return instant_remote_process(["du -b $this->backup_location | cut -f1"], $this->server, false); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function remove_old_backups(): void |  | ||||||
|     { |  | ||||||
|         if ($this->backup->number_of_backups_locally === 0) { |  | ||||||
|             $deletable = $this->backup->executions()->where('status', 'success'); |  | ||||||
|         } else { |  | ||||||
|             $deletable = $this->backup->executions()->where('status', 'success')->skip($this->backup->number_of_backups_locally - 1); |  | ||||||
|         } |  | ||||||
|         foreach ($deletable->get() as $execution) { |  | ||||||
|             delete_backup_locally($execution->filename, $this->server); |  | ||||||
|             $execution->delete(); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private function upload_to_s3(): void |     private function upload_to_s3(): void | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|   | |||||||
| @@ -19,6 +19,7 @@ use Illuminate\Contracts\Queue\ShouldBeEncrypted; | |||||||
| use Illuminate\Contracts\Queue\ShouldQueue; | use Illuminate\Contracts\Queue\ShouldQueue; | ||||||
| use Illuminate\Foundation\Bus\Dispatchable; | use Illuminate\Foundation\Bus\Dispatchable; | ||||||
| use Illuminate\Queue\InteractsWithQueue; | use Illuminate\Queue\InteractsWithQueue; | ||||||
|  | use Illuminate\Queue\Middleware\WithoutOverlapping; | ||||||
| use Illuminate\Queue\SerializesModels; | use Illuminate\Queue\SerializesModels; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| 
 | 
 | ||||||
| @@ -68,6 +69,11 @@ class PushServerUpdateJob implements ShouldBeEncrypted, ShouldQueue | |||||||
| 
 | 
 | ||||||
|     public bool $foundLogDrainContainer = false; |     public bool $foundLogDrainContainer = false; | ||||||
| 
 | 
 | ||||||
|  |     public function middleware(): array | ||||||
|  |     { | ||||||
|  |         return [(new WithoutOverlapping($this->server->uuid))->dontRelease()]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function backoff(): int |     public function backoff(): int | ||||||
|     { |     { | ||||||
|         return isDev() ? 1 : 3; |         return isDev() ? 1 : 3; | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ class SendMessageToPushoverJob implements ShouldBeEncrypted, ShouldQueue | |||||||
|     { |     { | ||||||
|         $response = Http::post('https://api.pushover.net/1/messages.json', $this->message->toPayload($this->token, $this->user)); |         $response = Http::post('https://api.pushover.net/1/messages.json', $this->message->toPayload($this->token, $this->user)); | ||||||
|         if ($response->failed()) { |         if ($response->failed()) { | ||||||
|             throw new \RuntimeException('Pushover notification failed with ' . $response->status() . ' status code.' . $response->body()); |             throw new \RuntimeException('Pushover notification failed with '.$response->status().' status code.'.$response->body()); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										104
									
								
								app/Jobs/VolumeCloneJob.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								app/Jobs/VolumeCloneJob.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Jobs; | ||||||
|  | 
 | ||||||
|  | use App\Models\LocalPersistentVolume; | ||||||
|  | use App\Models\Server; | ||||||
|  | use Illuminate\Bus\Queueable; | ||||||
|  | use Illuminate\Contracts\Queue\ShouldBeEncrypted; | ||||||
|  | use Illuminate\Contracts\Queue\ShouldQueue; | ||||||
|  | use Illuminate\Foundation\Bus\Dispatchable; | ||||||
|  | use Illuminate\Queue\InteractsWithQueue; | ||||||
|  | use Illuminate\Queue\SerializesModels; | ||||||
|  | 
 | ||||||
|  | class VolumeCloneJob implements ShouldBeEncrypted, ShouldQueue | ||||||
|  | { | ||||||
|  |     use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; | ||||||
|  | 
 | ||||||
|  |     protected string $cloneDir = '/data/coolify/clone'; | ||||||
|  | 
 | ||||||
|  |     public function __construct( | ||||||
|  |         protected string $sourceVolume, | ||||||
|  |         protected string $targetVolume, | ||||||
|  |         protected Server $sourceServer, | ||||||
|  |         protected ?Server $targetServer, | ||||||
|  |         protected LocalPersistentVolume $persistentVolume | ||||||
|  |     ) { | ||||||
|  |         $this->onQueue('high'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function handle() | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             if (! $this->targetServer || $this->targetServer->id === $this->sourceServer->id) { | ||||||
|  |                 $this->cloneLocalVolume(); | ||||||
|  |             } else { | ||||||
|  |                 $this->cloneRemoteVolume(); | ||||||
|  |             } | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             \Log::error("Failed to copy volume data for {$this->sourceVolume}: ".$e->getMessage()); | ||||||
|  |             throw $e; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected function cloneLocalVolume() | ||||||
|  |     { | ||||||
|  |         instant_remote_process([ | ||||||
|  |             "docker volume create $this->targetVolume", | ||||||
|  |             "docker run --rm -v $this->sourceVolume:/source -v $this->targetVolume:/target alpine sh -c 'cp -a /source/. /target/ && chown -R 1000:1000 /target'", | ||||||
|  |         ], $this->sourceServer); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     protected function cloneRemoteVolume() | ||||||
|  |     { | ||||||
|  |         $sourceCloneDir = "{$this->cloneDir}/{$this->sourceVolume}"; | ||||||
|  |         $targetCloneDir = "{$this->cloneDir}/{$this->targetVolume}"; | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             instant_remote_process([ | ||||||
|  |                 "mkdir -p $sourceCloneDir", | ||||||
|  |                 "chmod 777 $sourceCloneDir", | ||||||
|  |                 "docker run --rm -v $this->sourceVolume:/source -v $sourceCloneDir:/clone alpine sh -c 'cd /source && tar czf /clone/volume-data.tar.gz .'", | ||||||
|  |             ], $this->sourceServer); | ||||||
|  | 
 | ||||||
|  |             instant_remote_process([ | ||||||
|  |                 "mkdir -p $targetCloneDir", | ||||||
|  |                 "chmod 777 $targetCloneDir", | ||||||
|  |             ], $this->targetServer); | ||||||
|  | 
 | ||||||
|  |             instant_scp( | ||||||
|  |                 "$sourceCloneDir/volume-data.tar.gz", | ||||||
|  |                 "$targetCloneDir/volume-data.tar.gz", | ||||||
|  |                 $this->sourceServer, | ||||||
|  |                 $this->targetServer | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             instant_remote_process([ | ||||||
|  |                 "docker volume create $this->targetVolume", | ||||||
|  |                 "docker run --rm -v $this->targetVolume:/target -v $targetCloneDir:/clone alpine sh -c 'cd /target && tar xzf /clone/volume-data.tar.gz && chown -R 1000:1000 /target'", | ||||||
|  |             ], $this->targetServer); | ||||||
|  | 
 | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             \Log::error("Failed to clone volume {$this->sourceVolume} to {$this->targetVolume}: ".$e->getMessage()); | ||||||
|  |             throw $e; | ||||||
|  |         } finally { | ||||||
|  |             try { | ||||||
|  |                 instant_remote_process([ | ||||||
|  |                     "rm -rf $sourceCloneDir", | ||||||
|  |                 ], $this->sourceServer, false); | ||||||
|  |             } catch (\Exception $e) { | ||||||
|  |                 \Log::warning('Failed to clean up source server clone directory: '.$e->getMessage()); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             try { | ||||||
|  |                 if ($this->targetServer) { | ||||||
|  |                     instant_remote_process([ | ||||||
|  |                         "rm -rf $targetCloneDir", | ||||||
|  |                     ], $this->targetServer, false); | ||||||
|  |                 } | ||||||
|  |             } catch (\Exception $e) { | ||||||
|  |                 \Log::warning('Failed to clean up target server clone directory: '.$e->getMessage()); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -42,14 +42,8 @@ class ActivityMonitor extends Component | |||||||
|     public function polling() |     public function polling() | ||||||
|     { |     { | ||||||
|         $this->hydrateActivity(); |         $this->hydrateActivity(); | ||||||
|         // $this->setStatus(ProcessStatus::IN_PROGRESS);
 |  | ||||||
|         $exit_code = data_get($this->activity, 'properties.exitCode'); |         $exit_code = data_get($this->activity, 'properties.exitCode'); | ||||||
|         if ($exit_code !== null) { |         if ($exit_code !== null) { | ||||||
|             // if ($exit_code === 0) {
 |  | ||||||
|             //     // $this->setStatus(ProcessStatus::FINISHED);
 |  | ||||||
|             // } else {
 |  | ||||||
|             //     // $this->setStatus(ProcessStatus::ERROR);
 |  | ||||||
|             // }
 |  | ||||||
|             $this->isPollingActive = false; |             $this->isPollingActive = false; | ||||||
|             if ($exit_code === 0) { |             if ($exit_code === 0) { | ||||||
|                 if ($this->eventToDispatch !== null) { |                 if ($this->eventToDispatch !== null) { | ||||||
| @@ -70,12 +64,4 @@ class ActivityMonitor extends Component | |||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     // protected function setStatus($status)
 |  | ||||||
|     // {
 |  | ||||||
|     //     $this->activity->properties = $this->activity->properties->merge([
 |  | ||||||
|     //         'status' => $status,
 |  | ||||||
|     //     ]);
 |  | ||||||
|     //     $this->activity->save();
 |  | ||||||
|     // }
 |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -21,16 +21,28 @@ class Index extends Component | |||||||
| 
 | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|         if (! isCloud()) { |         if (! isCloud() && ! isDev()) { | ||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
| 
 |         if (Auth::id() !== 0 && ! session('impersonating')) { | ||||||
|         if (Auth::id() !== 0) { |  | ||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
|         $this->getSubscribers(); |         $this->getSubscribers(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function back() | ||||||
|  |     { | ||||||
|  |         if (session('impersonating')) { | ||||||
|  |             session()->forget('impersonating'); | ||||||
|  |             $user = User::find(0); | ||||||
|  |             $team_to_switch_to = $user->teams->first(); | ||||||
|  |             Auth::login($user); | ||||||
|  |             refreshSession($team_to_switch_to); | ||||||
|  | 
 | ||||||
|  |             return redirect(request()->header('Referer')); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function submitSearch() |     public function submitSearch() | ||||||
|     { |     { | ||||||
|         if ($this->search !== '') { |         if ($this->search !== '') { | ||||||
| @@ -52,9 +64,10 @@ class Index extends Component | |||||||
|         if (Auth::id() !== 0) { |         if (Auth::id() !== 0) { | ||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
|  |         session(['impersonating' => true]); | ||||||
|         $user = User::find($user_id); |         $user = User::find($user_id); | ||||||
|         $team_to_switch_to = $user->teams->first(); |         $team_to_switch_to = $user->teams->first(); | ||||||
|         Cache::forget("team:{$user->id}"); |         // Cache::forget("team:{$user->id}");
 | ||||||
|         Auth::login($user); |         Auth::login($user); | ||||||
|         refreshSession($team_to_switch_to); |         refreshSession($team_to_switch_to); | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ use App\Models\Server; | |||||||
| use App\Models\Team; | use App\Models\Team; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
|  | use Visus\Cuid2\Cuid2; | ||||||
| 
 | 
 | ||||||
| class Index extends Component | class Index extends Component | ||||||
| { | { | ||||||
| @@ -334,6 +335,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== | |||||||
|         $this->createdProject = Project::create([ |         $this->createdProject = Project::create([ | ||||||
|             'name' => 'My first project', |             'name' => 'My first project', | ||||||
|             'team_id' => currentTeam()->id, |             'team_id' => currentTeam()->id, | ||||||
|  |             'uuid' => (string) new Cuid2, | ||||||
|         ]); |         ]); | ||||||
|         $this->currentState = 'create-resource'; |         $this->currentState = 'create-resource'; | ||||||
|     } |     } | ||||||
| @@ -346,7 +348,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA== | |||||||
|             'project.resource.create', |             'project.resource.create', | ||||||
|             [ |             [ | ||||||
|                 'project_uuid' => $this->createdProject->uuid, |                 'project_uuid' => $this->createdProject->uuid, | ||||||
|                 'environment_name' => 'production', |                 'environment_uuid' => $this->createdProject->environments->first()->uuid, | ||||||
|                 'server' => $this->createdServer->id, |                 'server' => $this->createdServer->id, | ||||||
|             ] |             ] | ||||||
|         ); |         ); | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ use App\Models\Project; | |||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use Illuminate\Support\Collection; | use Illuminate\Support\Collection; | ||||||
| use Illuminate\Support\Facades\Artisan; | use Illuminate\Support\Facades\Artisan; | ||||||
|  | use Illuminate\Support\Facades\Redirect; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
| 
 | 
 | ||||||
| class Dashboard extends Component | class Dashboard extends Component | ||||||
| @@ -49,6 +50,20 @@ class Dashboard extends Component | |||||||
|         ])->sortBy('id')->groupBy('server_name')->toArray(); |         ])->sortBy('id')->groupBy('server_name')->toArray(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function navigateToProject($projectUuid) | ||||||
|  |     { | ||||||
|  |         $project = Project::where('uuid', $projectUuid)->first(); | ||||||
|  | 
 | ||||||
|  |         if ($project && $project->environments->count() === 1) { | ||||||
|  |             return Redirect::route('project.resource.index', [ | ||||||
|  |                 'project_uuid' => $projectUuid, | ||||||
|  |                 'environment_uuid' => $project->environments->first()->uuid, | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return Redirect::route('project.show', ['project_uuid' => $projectUuid]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function render() |     public function render() | ||||||
|     { |     { | ||||||
|         return view('livewire.dashboard'); |         return view('livewire.dashboard'); | ||||||
|   | |||||||
| @@ -83,9 +83,7 @@ class Docker extends Component | |||||||
|                     ]); |                     ]); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             $connectProxyToDockerNetworks = connectProxyToNetworks($this->selectedServer); |             $this->redirect(route('destination.show', $docker->uuid)); | ||||||
|             instant_remote_process($connectProxyToDockerNetworks, $this->selectedServer, false); |  | ||||||
|             $this->dispatch('reloadWindow'); |  | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ namespace App\Livewire\Project; | |||||||
| use App\Models\Project; | use App\Models\Project; | ||||||
| use Livewire\Attributes\Validate; | use Livewire\Attributes\Validate; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
|  | use Visus\Cuid2\Cuid2; | ||||||
| 
 | 
 | ||||||
| class AddEmpty extends Component | class AddEmpty extends Component | ||||||
| { | { | ||||||
| @@ -22,6 +23,7 @@ class AddEmpty extends Component | |||||||
|                 'name' => $this->name, |                 'name' => $this->name, | ||||||
|                 'description' => $this->description, |                 'description' => $this->description, | ||||||
|                 'team_id' => currentTeam()->id, |                 'team_id' => currentTeam()->id, | ||||||
|  |                 'uuid' => (string) new Cuid2, | ||||||
|             ]); |             ]); | ||||||
| 
 | 
 | ||||||
|             return redirect()->route('project.show', $project->uuid); |             return redirect()->route('project.show', $project->uuid); | ||||||
|   | |||||||
| @@ -3,43 +3,42 @@ | |||||||
| namespace App\Livewire\Project\Application; | namespace App\Livewire\Project\Application; | ||||||
| 
 | 
 | ||||||
| use App\Models\Application; | use App\Models\Application; | ||||||
| use App\Models\Server; |  | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
| 
 | 
 | ||||||
| class Configuration extends Component | class Configuration extends Component | ||||||
| { | { | ||||||
|  |     public $currentRoute; | ||||||
|  | 
 | ||||||
|     public Application $application; |     public Application $application; | ||||||
| 
 | 
 | ||||||
|  |     public $project; | ||||||
|  | 
 | ||||||
|  |     public $environment; | ||||||
|  | 
 | ||||||
|     public $servers; |     public $servers; | ||||||
| 
 | 
 | ||||||
|     protected $listeners = ['buildPackUpdated' => '$refresh']; |     protected $listeners = ['buildPackUpdated' => '$refresh']; | ||||||
| 
 | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|  |         $this->currentRoute = request()->route()->getName(); | ||||||
|         $project = currentTeam() |         $project = currentTeam() | ||||||
|             ->projects() |             ->projects() | ||||||
|             ->select('id', 'uuid', 'team_id') |             ->select('id', 'uuid', 'team_id') | ||||||
|             ->where('uuid', request()->route('project_uuid')) |             ->where('uuid', request()->route('project_uuid')) | ||||||
|             ->firstOrFail(); |             ->firstOrFail(); | ||||||
|         $environment = $project->environments() |         $environment = $project->environments() | ||||||
|             ->select('id', 'name', 'project_id') |             ->select('id', 'uuid', 'name', 'project_id') | ||||||
|             ->where('name', request()->route('environment_name')) |             ->where('uuid', request()->route('environment_uuid')) | ||||||
|             ->firstOrFail(); |             ->firstOrFail(); | ||||||
|         $application = $environment->applications() |         $application = $environment->applications() | ||||||
|             ->with(['destination']) |             ->with(['destination']) | ||||||
|             ->where('uuid', request()->route('application_uuid')) |             ->where('uuid', request()->route('application_uuid')) | ||||||
|             ->firstOrFail(); |             ->firstOrFail(); | ||||||
| 
 | 
 | ||||||
|  |         $this->project = $project; | ||||||
|  |         $this->environment = $environment; | ||||||
|         $this->application = $application; |         $this->application = $application; | ||||||
|         if ($application->destination && $application->destination->server) { |  | ||||||
|             $mainServer = $application->destination->server; |  | ||||||
|             $this->servers = Server::ownedByCurrentTeam() |  | ||||||
|                 ->select('id', 'name') |  | ||||||
|                 ->where('id', '!=', $mainServer->id) |  | ||||||
|                 ->get(); |  | ||||||
|         } else { |  | ||||||
|             $this->servers = collect(); |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function render() |     public function render() | ||||||
|   | |||||||
| @@ -34,7 +34,7 @@ class Index extends Component | |||||||
|         if (! $project) { |         if (! $project) { | ||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
|         $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); |         $environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first()->load(['applications']); | ||||||
|         if (! $environment) { |         if (! $environment) { | ||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -14,6 +14,8 @@ class Show extends Component | |||||||
| 
 | 
 | ||||||
|     public string $deployment_uuid; |     public string $deployment_uuid; | ||||||
| 
 | 
 | ||||||
|  |     public string $horizon_job_status; | ||||||
|  | 
 | ||||||
|     public $isKeepAliveOn = true; |     public $isKeepAliveOn = true; | ||||||
| 
 | 
 | ||||||
|     protected $listeners = ['refreshQueue']; |     protected $listeners = ['refreshQueue']; | ||||||
| @@ -26,7 +28,7 @@ class Show extends Component | |||||||
|         if (! $project) { |         if (! $project) { | ||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
|         $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); |         $environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first()->load(['applications']); | ||||||
|         if (! $environment) { |         if (! $environment) { | ||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
| @@ -34,25 +36,19 @@ class Show extends Component | |||||||
|         if (! $application) { |         if (! $application) { | ||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
|         // $activity = Activity::where('properties->type_uuid', '=', $deploymentUuid)->first();
 |  | ||||||
|         // if (!$activity) {
 |  | ||||||
|         //     return redirect()->route('project.application.deployment.index', [
 |  | ||||||
|         //         'project_uuid' => $project->uuid,
 |  | ||||||
|         //         'environment_name' => $environment->name,
 |  | ||||||
|         //         'application_uuid' => $application->uuid,
 |  | ||||||
|         //     ]);
 |  | ||||||
|         // }
 |  | ||||||
|         $application_deployment_queue = ApplicationDeploymentQueue::where('deployment_uuid', $deploymentUuid)->first(); |         $application_deployment_queue = ApplicationDeploymentQueue::where('deployment_uuid', $deploymentUuid)->first(); | ||||||
|         if (! $application_deployment_queue) { |         if (! $application_deployment_queue) { | ||||||
|             return redirect()->route('project.application.deployment.index', [ |             return redirect()->route('project.application.deployment.index', [ | ||||||
|                 'project_uuid' => $project->uuid, |                 'project_uuid' => $project->uuid, | ||||||
|                 'environment_name' => $environment->name, |                 'environment_uuid' => $environment->uuid, | ||||||
|                 'application_uuid' => $application->uuid, |                 'application_uuid' => $application->uuid, | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
|         $this->application = $application; |         $this->application = $application; | ||||||
|         $this->application_deployment_queue = $application_deployment_queue; |         $this->application_deployment_queue = $application_deployment_queue; | ||||||
|  |         $this->horizon_job_status = $this->application_deployment_queue->getHorizonJobStatus(); | ||||||
|         $this->deployment_uuid = $deploymentUuid; |         $this->deployment_uuid = $deploymentUuid; | ||||||
|  |         $this->isKeepAliveOn(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function refreshQueue() |     public function refreshQueue() | ||||||
| @@ -60,13 +56,21 @@ class Show extends Component | |||||||
|         $this->application_deployment_queue->refresh(); |         $this->application_deployment_queue->refresh(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     private function isKeepAliveOn() | ||||||
|  |     { | ||||||
|  |         if (data_get($this->application_deployment_queue, 'status') === 'finished' || data_get($this->application_deployment_queue, 'status') === 'failed') { | ||||||
|  |             $this->isKeepAliveOn = false; | ||||||
|  |         } else { | ||||||
|  |             $this->isKeepAliveOn = true; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function polling() |     public function polling() | ||||||
|     { |     { | ||||||
|         $this->dispatch('deploymentFinished'); |         $this->dispatch('deploymentFinished'); | ||||||
|         $this->application_deployment_queue->refresh(); |         $this->application_deployment_queue->refresh(); | ||||||
|         if (data_get($this->application_deployment_queue, 'status') === 'finished' || data_get($this->application_deployment_queue, 'status') === 'failed') { |         $this->horizon_job_status = $this->application_deployment_queue->getHorizonJobStatus(); | ||||||
|             $this->isKeepAliveOn = false; |         $this->isKeepAliveOn(); | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function getLogLinesProperty() |     public function getLogLinesProperty() | ||||||
|   | |||||||
| @@ -23,7 +23,7 @@ class DeploymentNavbar extends Component | |||||||
| 
 | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|         $this->application = Application::find($this->application_deployment_queue->application_id); |         $this->application = Application::ownedByCurrentTeam()->find($this->application_deployment_queue->application_id); | ||||||
|         $this->server = $this->application->destination->server; |         $this->server = $this->application->destination->server; | ||||||
|         $this->is_debug_enabled = $this->application->settings->is_debug_enabled; |         $this->is_debug_enabled = $this->application->settings->is_debug_enabled; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -153,7 +153,7 @@ class General extends Component | |||||||
|         $this->is_preserve_repository_enabled = $this->application->settings->is_preserve_repository_enabled; |         $this->is_preserve_repository_enabled = $this->application->settings->is_preserve_repository_enabled; | ||||||
|         $this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled; |         $this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled; | ||||||
|         $this->customLabels = $this->application->parseContainerLabels(); |         $this->customLabels = $this->application->parseContainerLabels(); | ||||||
|         if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) { |         if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && $this->application->settings->is_container_label_readonly_enabled === true) { | ||||||
|             $this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n"); |             $this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n"); | ||||||
|             $this->application->custom_labels = base64_encode($this->customLabels); |             $this->application->custom_labels = base64_encode($this->customLabels); | ||||||
|             $this->application->save(); |             $this->application->save(); | ||||||
| @@ -327,7 +327,7 @@ class General extends Component | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function set_redirect() |     public function setRedirect() | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count(); |             $has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count(); | ||||||
| @@ -360,10 +360,10 @@ class General extends Component | |||||||
|             if ($warning) { |             if ($warning) { | ||||||
|                 $this->dispatch('warning', __('warning.sslipdomain')); |                 $this->dispatch('warning', __('warning.sslipdomain')); | ||||||
|             } |             } | ||||||
|             $this->resetDefaultLabels(); |             // $this->resetDefaultLabels();
 | ||||||
| 
 | 
 | ||||||
|             if ($this->application->isDirty('redirect')) { |             if ($this->application->isDirty('redirect')) { | ||||||
|                 $this->set_redirect(); |                 $this->setRedirect(); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             $this->checkFqdns(); |             $this->checkFqdns(); | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ class Heading extends Component | |||||||
|     { |     { | ||||||
|         $this->parameters = [ |         $this->parameters = [ | ||||||
|             'project_uuid' => $this->application->project()->uuid, |             'project_uuid' => $this->application->project()->uuid, | ||||||
|             'environment_name' => $this->application->environment->name, |             'environment_uuid' => $this->application->environment->uuid, | ||||||
|             'application_uuid' => $this->application->uuid, |             'application_uuid' => $this->application->uuid, | ||||||
|         ]; |         ]; | ||||||
|         $lastDeployment = $this->application->get_last_successful_deployment(); |         $lastDeployment = $this->application->get_last_successful_deployment(); | ||||||
| @@ -94,7 +94,7 @@ class Heading extends Component | |||||||
|             'project_uuid' => $this->parameters['project_uuid'], |             'project_uuid' => $this->parameters['project_uuid'], | ||||||
|             'application_uuid' => $this->parameters['application_uuid'], |             'application_uuid' => $this->parameters['application_uuid'], | ||||||
|             'deployment_uuid' => $this->deploymentUuid, |             'deployment_uuid' => $this->deploymentUuid, | ||||||
|             'environment_name' => $this->parameters['environment_name'], |             'environment_uuid' => $this->parameters['environment_uuid'], | ||||||
|         ]); |         ]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -136,7 +136,7 @@ class Heading extends Component | |||||||
|             'project_uuid' => $this->parameters['project_uuid'], |             'project_uuid' => $this->parameters['project_uuid'], | ||||||
|             'application_uuid' => $this->parameters['application_uuid'], |             'application_uuid' => $this->parameters['application_uuid'], | ||||||
|             'deployment_uuid' => $this->deploymentUuid, |             'deployment_uuid' => $this->deploymentUuid, | ||||||
|             'environment_name' => $this->parameters['environment_name'], |             'environment_uuid' => $this->parameters['environment_uuid'], | ||||||
|         ]); |         ]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -171,7 +171,7 @@ class Previews extends Component | |||||||
|                 'project_uuid' => $this->parameters['project_uuid'], |                 'project_uuid' => $this->parameters['project_uuid'], | ||||||
|                 'application_uuid' => $this->parameters['application_uuid'], |                 'application_uuid' => $this->parameters['application_uuid'], | ||||||
|                 'deployment_uuid' => $this->deployment_uuid, |                 'deployment_uuid' => $this->deployment_uuid, | ||||||
|                 'environment_name' => $this->parameters['environment_name'], |                 'environment_uuid' => $this->parameters['environment_uuid'], | ||||||
|             ]); |             ]); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|   | |||||||
| @@ -37,7 +37,7 @@ class Rollback extends Component | |||||||
|             'project_uuid' => $this->parameters['project_uuid'], |             'project_uuid' => $this->parameters['project_uuid'], | ||||||
|             'application_uuid' => $this->parameters['application_uuid'], |             'application_uuid' => $this->parameters['application_uuid'], | ||||||
|             'deployment_uuid' => $deployment_uuid, |             'deployment_uuid' => $deployment_uuid, | ||||||
|             'environment_name' => $this->parameters['environment_name'], |             'environment_uuid' => $this->parameters['environment_uuid'], | ||||||
|         ]); |         ]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -2,6 +2,12 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Livewire\Project; | namespace App\Livewire\Project; | ||||||
| 
 | 
 | ||||||
|  | use App\Actions\Application\StopApplication; | ||||||
|  | use App\Actions\Database\StartDatabase; | ||||||
|  | use App\Actions\Database\StopDatabase; | ||||||
|  | use App\Actions\Service\StartService; | ||||||
|  | use App\Actions\Service\StopService; | ||||||
|  | use App\Jobs\VolumeCloneJob; | ||||||
| use App\Models\Environment; | use App\Models\Environment; | ||||||
| use App\Models\Project; | use App\Models\Project; | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| @@ -12,7 +18,7 @@ class CloneMe extends Component | |||||||
| { | { | ||||||
|     public string $project_uuid; |     public string $project_uuid; | ||||||
| 
 | 
 | ||||||
|     public string $environment_name; |     public string $environment_uuid; | ||||||
| 
 | 
 | ||||||
|     public int $project_id; |     public int $project_id; | ||||||
| 
 | 
 | ||||||
| @@ -34,6 +40,8 @@ class CloneMe extends Component | |||||||
| 
 | 
 | ||||||
|     public string $newName = ''; |     public string $newName = ''; | ||||||
| 
 | 
 | ||||||
|  |     public bool $cloneVolumeData = false; | ||||||
|  | 
 | ||||||
|     protected $messages = [ |     protected $messages = [ | ||||||
|         'selectedServer' => 'Please select a server.', |         'selectedServer' => 'Please select a server.', | ||||||
|         'selectedDestination' => 'Please select a server & destination.', |         'selectedDestination' => 'Please select a server & destination.', | ||||||
| @@ -44,12 +52,17 @@ class CloneMe extends Component | |||||||
|     { |     { | ||||||
|         $this->project_uuid = $project_uuid; |         $this->project_uuid = $project_uuid; | ||||||
|         $this->project = Project::where('uuid', $project_uuid)->firstOrFail(); |         $this->project = Project::where('uuid', $project_uuid)->firstOrFail(); | ||||||
|         $this->environment = $this->project->environments->where('name', $this->environment_name)->first(); |         $this->environment = $this->project->environments->where('uuid', $this->environment_uuid)->first(); | ||||||
|         $this->project_id = $this->project->id; |         $this->project_id = $this->project->id; | ||||||
|         $this->servers = currentTeam()->servers; |         $this->servers = currentTeam()->servers; | ||||||
|         $this->newName = str($this->project->name.'-clone-'.(string) new Cuid2)->slug(); |         $this->newName = str($this->project->name.'-clone-'.(string) new Cuid2)->slug(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function toggleVolumeCloning(bool $value) | ||||||
|  |     { | ||||||
|  |         $this->cloneVolumeData = $value; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function render() |     public function render() | ||||||
|     { |     { | ||||||
|         return view('livewire.project.clone-me'); |         return view('livewire.project.clone-me'); | ||||||
| @@ -89,6 +102,7 @@ class CloneMe extends Component | |||||||
|                 if ($this->environment->name !== 'production') { |                 if ($this->environment->name !== 'production') { | ||||||
|                     $project->environments()->create([ |                     $project->environments()->create([ | ||||||
|                         'name' => $this->environment->name, |                         'name' => $this->environment->name, | ||||||
|  |                         'uuid' => (string) new Cuid2, | ||||||
|                     ]); |                     ]); | ||||||
|                 } |                 } | ||||||
|                 $environment = $project->environments->where('name', $this->environment->name)->first(); |                 $environment = $project->environments->where('name', $this->environment->name)->first(); | ||||||
| @@ -100,41 +114,160 @@ class CloneMe extends Component | |||||||
|                 $project = $this->project; |                 $project = $this->project; | ||||||
|                 $environment = $this->project->environments()->create([ |                 $environment = $this->project->environments()->create([ | ||||||
|                     'name' => $this->newName, |                     'name' => $this->newName, | ||||||
|  |                     'uuid' => (string) new Cuid2, | ||||||
|                 ]); |                 ]); | ||||||
|             } |             } | ||||||
|             $applications = $this->environment->applications; |             $applications = $this->environment->applications; | ||||||
|             $databases = $this->environment->databases(); |             $databases = $this->environment->databases(); | ||||||
|             $services = $this->environment->services; |             $services = $this->environment->services; | ||||||
|             foreach ($applications as $application) { |             foreach ($applications as $application) { | ||||||
|  |                 $applicationSettings = $application->settings; | ||||||
|  | 
 | ||||||
|                 $uuid = (string) new Cuid2; |                 $uuid = (string) new Cuid2; | ||||||
|                 $newApplication = $application->replicate()->fill([ |                 $url = $application->fqdn; | ||||||
|  |                 if ($this->server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) { | ||||||
|  |                     $url = generateFqdn($this->server, $uuid); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $newApplication = $application->replicate([ | ||||||
|  |                     'id', | ||||||
|  |                     'created_at', | ||||||
|  |                     'updated_at', | ||||||
|  |                     'additional_servers_count', | ||||||
|  |                     'additional_networks_count', | ||||||
|  |                 ])->fill([ | ||||||
|                     'uuid' => $uuid, |                     'uuid' => $uuid, | ||||||
|                     'fqdn' => generateFqdn($this->server, $uuid), |                     'fqdn' => $url, | ||||||
|                     'status' => 'exited', |                     'status' => 'exited', | ||||||
|                     'environment_id' => $environment->id, |                     'environment_id' => $environment->id, | ||||||
|                     // This is not correct, but we need to set it to something
 |  | ||||||
|                     'destination_id' => $this->selectedDestination, |                     'destination_id' => $this->selectedDestination, | ||||||
|                 ]); |                 ]); | ||||||
|                 $newApplication->save(); |                 $newApplication->save(); | ||||||
|                 $environmentVaribles = $application->environment_variables()->get(); | 
 | ||||||
|                 foreach ($environmentVaribles as $environmentVarible) { |                 if ($newApplication->destination->server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) { | ||||||
|                     $newEnvironmentVariable = $environmentVarible->replicate()->fill([ |                     $customLabels = str(implode('|coolify|', generateLabelsApplication($newApplication)))->replace('|coolify|', "\n"); | ||||||
|  |                     $newApplication->custom_labels = base64_encode($customLabels); | ||||||
|  |                     $newApplication->save(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $newApplication->settings()->delete(); | ||||||
|  |                 if ($applicationSettings) { | ||||||
|  |                     $newApplicationSettings = $applicationSettings->replicate([ | ||||||
|  |                         'id', | ||||||
|  |                         'created_at', | ||||||
|  |                         'updated_at', | ||||||
|  |                     ])->fill([ | ||||||
|                         'application_id' => $newApplication->id, |                         'application_id' => $newApplication->id, | ||||||
|                     ]); |                     ]); | ||||||
|                     $newEnvironmentVariable->save(); |                     $newApplicationSettings->save(); | ||||||
|                 } |                 } | ||||||
|  | 
 | ||||||
|  |                 $tags = $application->tags; | ||||||
|  |                 foreach ($tags as $tag) { | ||||||
|  |                     $newApplication->tags()->attach($tag->id); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $scheduledTasks = $application->scheduled_tasks()->get(); | ||||||
|  |                 foreach ($scheduledTasks as $task) { | ||||||
|  |                     $newTask = $task->replicate([ | ||||||
|  |                         'id', | ||||||
|  |                         'created_at', | ||||||
|  |                         'updated_at', | ||||||
|  |                     ])->fill([ | ||||||
|  |                         'uuid' => (string) new Cuid2, | ||||||
|  |                         'application_id' => $newApplication->id, | ||||||
|  |                         'team_id' => currentTeam()->id, | ||||||
|  |                     ]); | ||||||
|  |                     $newTask->save(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $applicationPreviews = $application->previews()->get(); | ||||||
|  |                 foreach ($applicationPreviews as $preview) { | ||||||
|  |                     $newPreview = $preview->replicate([ | ||||||
|  |                         'id', | ||||||
|  |                         'created_at', | ||||||
|  |                         'updated_at', | ||||||
|  |                     ])->fill([ | ||||||
|  |                         'application_id' => $newApplication->id, | ||||||
|  |                         'status' => 'exited', | ||||||
|  |                     ]); | ||||||
|  |                     $newPreview->save(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|                 $persistentVolumes = $application->persistentStorages()->get(); |                 $persistentVolumes = $application->persistentStorages()->get(); | ||||||
|                 foreach ($persistentVolumes as $volume) { |                 foreach ($persistentVolumes as $volume) { | ||||||
|                     $newPersistentVolume = $volume->replicate()->fill([ |                     $newName = ''; | ||||||
|                         'name' => $newApplication->uuid.'-'.str($volume->name)->afterLast('-'), |                     if (str_starts_with($volume->name, $application->uuid)) { | ||||||
|  |                         $newName = str($volume->name)->replace($application->uuid, $newApplication->uuid); | ||||||
|  |                     } else { | ||||||
|  |                         $newName = $newApplication->uuid.'-'.$volume->name; | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     $newPersistentVolume = $volume->replicate([ | ||||||
|  |                         'id', | ||||||
|  |                         'created_at', | ||||||
|  |                         'updated_at', | ||||||
|  |                     ])->fill([ | ||||||
|  |                         'name' => $newName, | ||||||
|                         'resource_id' => $newApplication->id, |                         'resource_id' => $newApplication->id, | ||||||
|                     ]); |                     ]); | ||||||
|                     $newPersistentVolume->save(); |                     $newPersistentVolume->save(); | ||||||
|  | 
 | ||||||
|  |                     if ($this->cloneVolumeData) { | ||||||
|  |                         try { | ||||||
|  |                             StopApplication::dispatch($application, false, false); | ||||||
|  |                             $sourceVolume = $volume->name; | ||||||
|  |                             $targetVolume = $newPersistentVolume->name; | ||||||
|  |                             $sourceServer = $application->destination->server; | ||||||
|  |                             $targetServer = $newApplication->destination->server; | ||||||
|  | 
 | ||||||
|  |                             VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); | ||||||
|  | 
 | ||||||
|  |                             queue_application_deployment( | ||||||
|  |                                 deployment_uuid: (string) new Cuid2, | ||||||
|  |                                 application: $application, | ||||||
|  |                                 server: $sourceServer, | ||||||
|  |                                 destination: $application->destination, | ||||||
|  |                                 no_questions_asked: true | ||||||
|  |                             ); | ||||||
|  |                         } catch (\Exception $e) { | ||||||
|  |                             \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $fileStorages = $application->fileStorages()->get(); | ||||||
|  |                 foreach ($fileStorages as $storage) { | ||||||
|  |                     $newStorage = $storage->replicate([ | ||||||
|  |                         'id', | ||||||
|  |                         'created_at', | ||||||
|  |                         'updated_at', | ||||||
|  |                     ])->fill([ | ||||||
|  |                         'resource_id' => $newApplication->id, | ||||||
|  |                     ]); | ||||||
|  |                     $newStorage->save(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $environmentVaribles = $application->environment_variables()->get(); | ||||||
|  |                 foreach ($environmentVaribles as $environmentVarible) { | ||||||
|  |                     $newEnvironmentVariable = $environmentVarible->replicate([ | ||||||
|  |                         'id', | ||||||
|  |                         'created_at', | ||||||
|  |                         'updated_at', | ||||||
|  |                     ])->fill([ | ||||||
|  |                         'resourceable_id' => $newApplication->id, | ||||||
|  |                     ]); | ||||||
|  |                     $newEnvironmentVariable->save(); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|             foreach ($databases as $database) { |             foreach ($databases as $database) { | ||||||
|                 $uuid = (string) new Cuid2; |                 $uuid = (string) new Cuid2; | ||||||
|                 $newDatabase = $database->replicate()->fill([ |                 $newDatabase = $database->replicate([ | ||||||
|  |                     'id', | ||||||
|  |                     'created_at', | ||||||
|  |                     'updated_at', | ||||||
|  |                 ])->fill([ | ||||||
|                     'uuid' => $uuid, |                     'uuid' => $uuid, | ||||||
|                     'status' => 'exited', |                     'status' => 'exited', | ||||||
|                     'started_at' => null, |                     'started_at' => null, | ||||||
| @@ -142,51 +275,294 @@ class CloneMe extends Component | |||||||
|                     'destination_id' => $this->selectedDestination, |                     'destination_id' => $this->selectedDestination, | ||||||
|                 ]); |                 ]); | ||||||
|                 $newDatabase->save(); |                 $newDatabase->save(); | ||||||
|  | 
 | ||||||
|  |                 $tags = $database->tags; | ||||||
|  |                 foreach ($tags as $tag) { | ||||||
|  |                     $newDatabase->tags()->attach($tag->id); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $newDatabase->persistentStorages()->delete(); | ||||||
|  |                 $persistentVolumes = $database->persistentStorages()->get(); | ||||||
|  |                 foreach ($persistentVolumes as $volume) { | ||||||
|  |                     $originalName = $volume->name; | ||||||
|  |                     $newName = ''; | ||||||
|  | 
 | ||||||
|  |                     if (str_starts_with($originalName, 'postgres-data-')) { | ||||||
|  |                         $newName = 'postgres-data-'.$newDatabase->uuid; | ||||||
|  |                     } elseif (str_starts_with($originalName, 'mysql-data-')) { | ||||||
|  |                         $newName = 'mysql-data-'.$newDatabase->uuid; | ||||||
|  |                     } elseif (str_starts_with($originalName, 'redis-data-')) { | ||||||
|  |                         $newName = 'redis-data-'.$newDatabase->uuid; | ||||||
|  |                     } elseif (str_starts_with($originalName, 'clickhouse-data-')) { | ||||||
|  |                         $newName = 'clickhouse-data-'.$newDatabase->uuid; | ||||||
|  |                     } elseif (str_starts_with($originalName, 'mariadb-data-')) { | ||||||
|  |                         $newName = 'mariadb-data-'.$newDatabase->uuid; | ||||||
|  |                     } elseif (str_starts_with($originalName, 'mongodb-data-')) { | ||||||
|  |                         $newName = 'mongodb-data-'.$newDatabase->uuid; | ||||||
|  |                     } elseif (str_starts_with($originalName, 'keydb-data-')) { | ||||||
|  |                         $newName = 'keydb-data-'.$newDatabase->uuid; | ||||||
|  |                     } elseif (str_starts_with($originalName, 'dragonfly-data-')) { | ||||||
|  |                         $newName = 'dragonfly-data-'.$newDatabase->uuid; | ||||||
|  |                     } else { | ||||||
|  |                         if (str_starts_with($volume->name, $database->uuid)) { | ||||||
|  |                             $newName = str($volume->name)->replace($database->uuid, $newDatabase->uuid); | ||||||
|  |                         } else { | ||||||
|  |                             $newName = $newDatabase->uuid.'-'.$volume->name; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     $newPersistentVolume = $volume->replicate([ | ||||||
|  |                         'id', | ||||||
|  |                         'created_at', | ||||||
|  |                         'updated_at', | ||||||
|  |                     ])->fill([ | ||||||
|  |                         'name' => $newName, | ||||||
|  |                         'resource_id' => $newDatabase->id, | ||||||
|  |                     ]); | ||||||
|  |                     $newPersistentVolume->save(); | ||||||
|  | 
 | ||||||
|  |                     if ($this->cloneVolumeData) { | ||||||
|  |                         try { | ||||||
|  |                             StopDatabase::dispatch($database); | ||||||
|  |                             $sourceVolume = $volume->name; | ||||||
|  |                             $targetVolume = $newPersistentVolume->name; | ||||||
|  |                             $sourceServer = $database->destination->server; | ||||||
|  |                             $targetServer = $newDatabase->destination->server; | ||||||
|  | 
 | ||||||
|  |                             VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); | ||||||
|  | 
 | ||||||
|  |                             StartDatabase::dispatch($database); | ||||||
|  |                         } catch (\Exception $e) { | ||||||
|  |                             \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $fileStorages = $database->fileStorages()->get(); | ||||||
|  |                 foreach ($fileStorages as $storage) { | ||||||
|  |                     $newStorage = $storage->replicate([ | ||||||
|  |                         'id', | ||||||
|  |                         'created_at', | ||||||
|  |                         'updated_at', | ||||||
|  |                     ])->fill([ | ||||||
|  |                         'resource_id' => $newDatabase->id, | ||||||
|  |                     ]); | ||||||
|  |                     $newStorage->save(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $scheduledBackups = $database->scheduledBackups()->get(); | ||||||
|  |                 foreach ($scheduledBackups as $backup) { | ||||||
|  |                     $uuid = (string) new Cuid2; | ||||||
|  |                     $newBackup = $backup->replicate([ | ||||||
|  |                         'id', | ||||||
|  |                         'created_at', | ||||||
|  |                         'updated_at', | ||||||
|  |                     ])->fill([ | ||||||
|  |                         'uuid' => $uuid, | ||||||
|  |                         'database_id' => $newDatabase->id, | ||||||
|  |                         'database_type' => $newDatabase->getMorphClass(), | ||||||
|  |                         'team_id' => currentTeam()->id, | ||||||
|  |                     ]); | ||||||
|  |                     $newBackup->save(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|                 $environmentVaribles = $database->environment_variables()->get(); |                 $environmentVaribles = $database->environment_variables()->get(); | ||||||
|                 foreach ($environmentVaribles as $environmentVarible) { |                 foreach ($environmentVaribles as $environmentVarible) { | ||||||
|                     $payload = []; |                     $payload = []; | ||||||
|                     if ($database->type() === 'standalone-postgresql') { |                     $payload['resourceable_id'] = $newDatabase->id; | ||||||
|                         $payload['standalone_postgresql_id'] = $newDatabase->id; |                     $payload['resourceable_type'] = $newDatabase->getMorphClass(); | ||||||
|                     } elseif ($database->type() === 'standalone-redis') { |                     $newEnvironmentVariable = $environmentVarible->replicate([ | ||||||
|                         $payload['standalone_redis_id'] = $newDatabase->id; |                         'id', | ||||||
|                     } elseif ($database->type() === 'standalone-mongodb') { |                         'created_at', | ||||||
|                         $payload['standalone_mongodb_id'] = $newDatabase->id; |                         'updated_at', | ||||||
|                     } elseif ($database->type() === 'standalone-mysql') { |                     ])->fill($payload); | ||||||
|                         $payload['standalone_mysql_id'] = $newDatabase->id; |  | ||||||
|                     } elseif ($database->type() === 'standalone-mariadb') { |  | ||||||
|                         $payload['standalone_mariadb_id'] = $newDatabase->id; |  | ||||||
|                     } |  | ||||||
|                     $newEnvironmentVariable = $environmentVarible->replicate()->fill($payload); |  | ||||||
|                     $newEnvironmentVariable->save(); |                     $newEnvironmentVariable->save(); | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|             foreach ($services as $service) { |             foreach ($services as $service) { | ||||||
|                 $uuid = (string) new Cuid2; |                 $uuid = (string) new Cuid2; | ||||||
|                 $newService = $service->replicate()->fill([ |                 $newService = $service->replicate([ | ||||||
|  |                     'id', | ||||||
|  |                     'created_at', | ||||||
|  |                     'updated_at', | ||||||
|  |                 ])->fill([ | ||||||
|                     'uuid' => $uuid, |                     'uuid' => $uuid, | ||||||
|                     'environment_id' => $environment->id, |                     'environment_id' => $environment->id, | ||||||
|                     'destination_id' => $this->selectedDestination, |                     'destination_id' => $this->selectedDestination, | ||||||
|                 ]); |                 ]); | ||||||
|                 $newService->save(); |                 $newService->save(); | ||||||
|  | 
 | ||||||
|  |                 $tags = $service->tags; | ||||||
|  |                 foreach ($tags as $tag) { | ||||||
|  |                     $newService->tags()->attach($tag->id); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $scheduledTasks = $service->scheduled_tasks()->get(); | ||||||
|  |                 foreach ($scheduledTasks as $task) { | ||||||
|  |                     $newTask = $task->replicate([ | ||||||
|  |                         'id', | ||||||
|  |                         'created_at', | ||||||
|  |                         'updated_at', | ||||||
|  |                     ])->fill([ | ||||||
|  |                         'uuid' => (string) new Cuid2, | ||||||
|  |                         'service_id' => $newService->id, | ||||||
|  |                         'team_id' => currentTeam()->id, | ||||||
|  |                     ]); | ||||||
|  |                     $newTask->save(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $environmentVariables = $service->environment_variables()->get(); | ||||||
|  |                 foreach ($environmentVariables as $environmentVariable) { | ||||||
|  |                     $newEnvironmentVariable = $environmentVariable->replicate([ | ||||||
|  |                         'id', | ||||||
|  |                         'created_at', | ||||||
|  |                         'updated_at', | ||||||
|  |                     ])->fill([ | ||||||
|  |                         'resourceable_id' => $newService->id, | ||||||
|  |                         'resourceable_type' => $newService->getMorphClass(), | ||||||
|  |                     ]); | ||||||
|  |                     $newEnvironmentVariable->save(); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|                 foreach ($newService->applications() as $application) { |                 foreach ($newService->applications() as $application) { | ||||||
|                     $application->update([ |                     $application->update([ | ||||||
|                         'status' => 'exited', |                         'status' => 'exited', | ||||||
|                     ]); |                     ]); | ||||||
|  | 
 | ||||||
|  |                     $persistentVolumes = $application->persistentStorages()->get(); | ||||||
|  |                     foreach ($persistentVolumes as $volume) { | ||||||
|  |                         $newName = ''; | ||||||
|  |                         if (str_starts_with($volume->name, $application->uuid)) { | ||||||
|  |                             $newName = str($volume->name)->replace($application->uuid, $application->uuid); | ||||||
|  |                         } else { | ||||||
|  |                             $newName = $application->uuid.'-'.$volume->name; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         $newPersistentVolume = $volume->replicate([ | ||||||
|  |                             'id', | ||||||
|  |                             'created_at', | ||||||
|  |                             'updated_at', | ||||||
|  |                         ])->fill([ | ||||||
|  |                             'name' => $newName, | ||||||
|  |                             'resource_id' => $application->id, | ||||||
|  |                         ]); | ||||||
|  |                         $newPersistentVolume->save(); | ||||||
|  | 
 | ||||||
|  |                         if ($this->cloneVolumeData) { | ||||||
|  |                             try { | ||||||
|  |                                 StopService::dispatch($application, false, false); | ||||||
|  |                                 $sourceVolume = $volume->name; | ||||||
|  |                                 $targetVolume = $newPersistentVolume->name; | ||||||
|  |                                 $sourceServer = $application->service->destination->server; | ||||||
|  |                                 $targetServer = $newService->destination->server; | ||||||
|  | 
 | ||||||
|  |                                 VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); | ||||||
|  | 
 | ||||||
|  |                                 StartService::dispatch($application); | ||||||
|  |                             } catch (\Exception $e) { | ||||||
|  |                                 \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     $fileStorages = $application->fileStorages()->get(); | ||||||
|  |                     foreach ($fileStorages as $storage) { | ||||||
|  |                         $newStorage = $storage->replicate([ | ||||||
|  |                             'id', | ||||||
|  |                             'created_at', | ||||||
|  |                             'updated_at', | ||||||
|  |                         ])->fill([ | ||||||
|  |                             'resource_id' => $application->id, | ||||||
|  |                         ]); | ||||||
|  |                         $newStorage->save(); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|  | 
 | ||||||
|                 foreach ($newService->databases() as $database) { |                 foreach ($newService->databases() as $database) { | ||||||
|                     $database->update([ |                     $database->update([ | ||||||
|                         'status' => 'exited', |                         'status' => 'exited', | ||||||
|                     ]); |                     ]); | ||||||
|  | 
 | ||||||
|  |                     $persistentVolumes = $database->persistentStorages()->get(); | ||||||
|  |                     foreach ($persistentVolumes as $volume) { | ||||||
|  |                         $newName = ''; | ||||||
|  |                         if (str_starts_with($volume->name, $database->uuid)) { | ||||||
|  |                             $newName = str($volume->name)->replace($database->uuid, $database->uuid); | ||||||
|  |                         } else { | ||||||
|  |                             $newName = $database->uuid.'-'.$volume->name; | ||||||
|  |                         } | ||||||
|  | 
 | ||||||
|  |                         $newPersistentVolume = $volume->replicate([ | ||||||
|  |                             'id', | ||||||
|  |                             'created_at', | ||||||
|  |                             'updated_at', | ||||||
|  |                         ])->fill([ | ||||||
|  |                             'name' => $newName, | ||||||
|  |                             'resource_id' => $database->id, | ||||||
|  |                         ]); | ||||||
|  |                         $newPersistentVolume->save(); | ||||||
|  | 
 | ||||||
|  |                         if ($this->cloneVolumeData) { | ||||||
|  |                             try { | ||||||
|  |                                 StopService::dispatch($database->service, false, false); | ||||||
|  |                                 $sourceVolume = $volume->name; | ||||||
|  |                                 $targetVolume = $newPersistentVolume->name; | ||||||
|  |                                 $sourceServer = $database->service->destination->server; | ||||||
|  |                                 $targetServer = $newService->destination->server; | ||||||
|  | 
 | ||||||
|  |                                 VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); | ||||||
|  | 
 | ||||||
|  |                                 StartService::dispatch($database->service); | ||||||
|  |                             } catch (\Exception $e) { | ||||||
|  |                                 \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); | ||||||
|  |                             } | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     $fileStorages = $database->fileStorages()->get(); | ||||||
|  |                     foreach ($fileStorages as $storage) { | ||||||
|  |                         $newStorage = $storage->replicate([ | ||||||
|  |                             'id', | ||||||
|  |                             'created_at', | ||||||
|  |                             'updated_at', | ||||||
|  |                         ])->fill([ | ||||||
|  |                             'resource_id' => $database->id, | ||||||
|  |                         ]); | ||||||
|  |                         $newStorage->save(); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     $scheduledBackups = $database->scheduledBackups()->get(); | ||||||
|  |                     foreach ($scheduledBackups as $backup) { | ||||||
|  |                         $uuid = (string) new Cuid2; | ||||||
|  |                         $newBackup = $backup->replicate([ | ||||||
|  |                             'id', | ||||||
|  |                             'created_at', | ||||||
|  |                             'updated_at', | ||||||
|  |                         ])->fill([ | ||||||
|  |                             'uuid' => $uuid, | ||||||
|  |                             'database_id' => $database->id, | ||||||
|  |                             'database_type' => $database->getMorphClass(), | ||||||
|  |                             'team_id' => currentTeam()->id, | ||||||
|  |                         ]); | ||||||
|  |                         $newBackup->save(); | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|  | 
 | ||||||
|                 $newService->parse(); |                 $newService->parse(); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             return redirect()->route('project.resource.index', [ |  | ||||||
|                 'project_uuid' => $project->uuid, |  | ||||||
|                 'environment_name' => $environment->name, |  | ||||||
|             ]); |  | ||||||
|         } catch (\Exception $e) { |         } catch (\Exception $e) { | ||||||
|             return handleError($e, $this); |             handleError($e, $this); | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         } finally { | ||||||
|  |             if (! isset($e)) { | ||||||
|  |                 return redirect()->route('project.resource.index', [ | ||||||
|  |                     'project_uuid' => $project->uuid, | ||||||
|  |                     'environment_uuid' => $environment->uuid, | ||||||
|  |                 ]); | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ class Execution extends Component | |||||||
|         if (! $project) { |         if (! $project) { | ||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
|         $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); |         $environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first()->load(['applications']); | ||||||
|         if (! $environment) { |         if (! $environment) { | ||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -14,7 +14,7 @@ class Index extends Component | |||||||
|         if (! $project) { |         if (! $project) { | ||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
|         $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); |         $environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first()->load(['applications']); | ||||||
|         if (! $environment) { |         if (! $environment) { | ||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
| @@ -31,7 +31,7 @@ class Index extends Component | |||||||
|         ) { |         ) { | ||||||
|             return redirect()->route('project.database.configuration', [ |             return redirect()->route('project.database.configuration', [ | ||||||
|                 'project_uuid' => $project->uuid, |                 'project_uuid' => $project->uuid, | ||||||
|                 'environment_name' => $environment->name, |                 'environment_uuid' => $environment->uuid, | ||||||
|                 'database_uuid' => $database->uuid, |                 'database_uuid' => $database->uuid, | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -40,8 +40,26 @@ class BackupEdit extends Component | |||||||
|     #[Validate(['required', 'string'])]
 |     #[Validate(['required', 'string'])]
 | ||||||
|     public string $frequency = ''; |     public string $frequency = ''; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['required', 'integer', 'min:1'])]
 |     #[Validate(['string'])]
 | ||||||
|     public int $numberOfBackupsLocally = 1; |     public string $timezone = ''; | ||||||
|  | 
 | ||||||
|  |     #[Validate(['required', 'integer'])]
 | ||||||
|  |     public int $databaseBackupRetentionAmountLocally = 0; | ||||||
|  | 
 | ||||||
|  |     #[Validate(['required', 'integer'])]
 | ||||||
|  |     public ?int $databaseBackupRetentionDaysLocally = 0; | ||||||
|  | 
 | ||||||
|  |     #[Validate(['required', 'numeric', 'min:0'])]
 | ||||||
|  |     public ?float $databaseBackupRetentionMaxStorageLocally = 0; | ||||||
|  | 
 | ||||||
|  |     #[Validate(['required', 'integer'])]
 | ||||||
|  |     public ?int $databaseBackupRetentionAmountS3 = 0; | ||||||
|  | 
 | ||||||
|  |     #[Validate(['required', 'integer'])]
 | ||||||
|  |     public ?int $databaseBackupRetentionDaysS3 = 0; | ||||||
|  | 
 | ||||||
|  |     #[Validate(['required', 'numeric', 'min:0'])]
 | ||||||
|  |     public ?float $databaseBackupRetentionMaxStorageS3 = 0; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['required', 'boolean'])]
 |     #[Validate(['required', 'boolean'])]
 | ||||||
|     public bool $saveS3 = false; |     public bool $saveS3 = false; | ||||||
| @@ -68,19 +86,30 @@ class BackupEdit extends Component | |||||||
|     public function syncData(bool $toModel = false) |     public function syncData(bool $toModel = false) | ||||||
|     { |     { | ||||||
|         if ($toModel) { |         if ($toModel) { | ||||||
|             $this->customValidate(); |  | ||||||
|             $this->backup->enabled = $this->backupEnabled; |             $this->backup->enabled = $this->backupEnabled; | ||||||
|             $this->backup->frequency = $this->frequency; |             $this->backup->frequency = $this->frequency; | ||||||
|             $this->backup->number_of_backups_locally = $this->numberOfBackupsLocally; |             $this->backup->database_backup_retention_amount_locally = $this->databaseBackupRetentionAmountLocally; | ||||||
|  |             $this->backup->database_backup_retention_days_locally = $this->databaseBackupRetentionDaysLocally; | ||||||
|  |             $this->backup->database_backup_retention_max_storage_locally = $this->databaseBackupRetentionMaxStorageLocally; | ||||||
|  |             $this->backup->database_backup_retention_amount_s3 = $this->databaseBackupRetentionAmountS3; | ||||||
|  |             $this->backup->database_backup_retention_days_s3 = $this->databaseBackupRetentionDaysS3; | ||||||
|  |             $this->backup->database_backup_retention_max_storage_s3 = $this->databaseBackupRetentionMaxStorageS3; | ||||||
|             $this->backup->save_s3 = $this->saveS3; |             $this->backup->save_s3 = $this->saveS3; | ||||||
|             $this->backup->s3_storage_id = $this->s3StorageId; |             $this->backup->s3_storage_id = $this->s3StorageId; | ||||||
|             $this->backup->databases_to_backup = $this->databasesToBackup; |             $this->backup->databases_to_backup = $this->databasesToBackup; | ||||||
|             $this->backup->dump_all = $this->dumpAll; |             $this->backup->dump_all = $this->dumpAll; | ||||||
|  |             $this->customValidate(); | ||||||
|             $this->backup->save(); |             $this->backup->save(); | ||||||
|         } else { |         } else { | ||||||
|             $this->backupEnabled = $this->backup->enabled; |             $this->backupEnabled = $this->backup->enabled; | ||||||
|             $this->frequency = $this->backup->frequency; |             $this->frequency = $this->backup->frequency; | ||||||
|             $this->numberOfBackupsLocally = $this->backup->number_of_backups_locally; |             $this->timezone = data_get($this->backup->server(), 'settings.server_timezone', 'Instance timezone'); | ||||||
|  |             $this->databaseBackupRetentionAmountLocally = $this->backup->database_backup_retention_amount_locally; | ||||||
|  |             $this->databaseBackupRetentionDaysLocally = $this->backup->database_backup_retention_days_locally; | ||||||
|  |             $this->databaseBackupRetentionMaxStorageLocally = $this->backup->database_backup_retention_max_storage_locally; | ||||||
|  |             $this->databaseBackupRetentionAmountS3 = $this->backup->database_backup_retention_amount_s3; | ||||||
|  |             $this->databaseBackupRetentionDaysS3 = $this->backup->database_backup_retention_days_s3; | ||||||
|  |             $this->databaseBackupRetentionMaxStorageS3 = $this->backup->database_backup_retention_max_storage_s3; | ||||||
|             $this->saveS3 = $this->backup->save_s3; |             $this->saveS3 = $this->backup->save_s3; | ||||||
|             $this->s3StorageId = $this->backup->s3_storage_id; |             $this->s3StorageId = $this->backup->s3_storage_id; | ||||||
|             $this->databasesToBackup = $this->backup->databases_to_backup; |             $this->databasesToBackup = $this->backup->databases_to_backup; | ||||||
| @@ -99,11 +128,29 @@ class BackupEdit extends Component | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             if ($this->delete_associated_backups_locally) { |             $server = null; | ||||||
|                 $this->deleteAssociatedBackupsLocally(); |             if ($this->backup->database instanceof \App\Models\ServiceDatabase) { | ||||||
|  |                 $server = $this->backup->database->service->destination->server; | ||||||
|  |             } elseif ($this->backup->database->destination && $this->backup->database->destination->server) { | ||||||
|  |                 $server = $this->backup->database->destination->server; | ||||||
|             } |             } | ||||||
|             if ($this->delete_associated_backups_s3) { | 
 | ||||||
|                 $this->deleteAssociatedBackupsS3(); |             $filenames = $this->backup->executions() | ||||||
|  |                 ->whereNotNull('filename') | ||||||
|  |                 ->where('filename', '!=', '') | ||||||
|  |                 ->where('scheduled_database_backup_id', $this->backup->id) | ||||||
|  |                 ->pluck('filename') | ||||||
|  |                 ->filter() | ||||||
|  |                 ->all(); | ||||||
|  | 
 | ||||||
|  |             if (! empty($filenames)) { | ||||||
|  |                 if ($this->delete_associated_backups_locally && $server) { | ||||||
|  |                     deleteBackupsLocally($filenames, $server); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 if ($this->delete_associated_backups_s3 && $this->backup->s3) { | ||||||
|  |                     deleteBackupsS3($filenames, $this->backup->s3); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             $this->backup->delete(); |             $this->backup->delete(); | ||||||
| @@ -119,7 +166,9 @@ class BackupEdit extends Component | |||||||
|             } else { |             } else { | ||||||
|                 return redirect()->route('project.database.backup.index', $this->parameters); |                 return redirect()->route('project.database.backup.index', $this->parameters); | ||||||
|             } |             } | ||||||
|         } catch (\Throwable $e) { |         } catch (\Exception $e) { | ||||||
|  |             $this->dispatch('error', 'Failed to delete backup: '.$e->getMessage()); | ||||||
|  | 
 | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -156,63 +205,12 @@ class BackupEdit extends Component | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function deleteAssociatedBackupsLocally() |  | ||||||
|     { |  | ||||||
|         $executions = $this->backup->executions; |  | ||||||
|         $backupFolder = null; |  | ||||||
| 
 |  | ||||||
|         foreach ($executions as $execution) { |  | ||||||
|             if ($this->backup->database->getMorphClass() === \App\Models\ServiceDatabase::class) { |  | ||||||
|                 $server = $this->backup->database->service->destination->server; |  | ||||||
|             } else { |  | ||||||
|                 $server = $this->backup->database->destination->server; |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             if (! $backupFolder) { |  | ||||||
|                 $backupFolder = dirname($execution->filename); |  | ||||||
|             } |  | ||||||
| 
 |  | ||||||
|             delete_backup_locally($execution->filename, $server); |  | ||||||
|             $execution->delete(); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (str($backupFolder)->isNotEmpty()) { |  | ||||||
|             $this->deleteEmptyBackupFolder($backupFolder, $server); |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private function deleteAssociatedBackupsS3() |  | ||||||
|     { |  | ||||||
|         //Add function to delete backups from S3
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private function deleteAssociatedBackupsSftp() |  | ||||||
|     { |  | ||||||
|         //Add function to delete backups from SFTP
 |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private function deleteEmptyBackupFolder($folderPath, $server) |  | ||||||
|     { |  | ||||||
|         $checkEmpty = instant_remote_process(["[ -z \"$(ls -A '$folderPath')\" ] && echo 'empty' || echo 'not empty'"], $server); |  | ||||||
| 
 |  | ||||||
|         if (trim($checkEmpty) === 'empty') { |  | ||||||
|             instant_remote_process(["rmdir '$folderPath'"], $server); |  | ||||||
| 
 |  | ||||||
|             $parentFolder = dirname($folderPath); |  | ||||||
|             $checkParentEmpty = instant_remote_process(["[ -z \"$(ls -A '$parentFolder')\" ] && echo 'empty' || echo 'not empty'"], $server); |  | ||||||
| 
 |  | ||||||
|             if (trim($checkParentEmpty) === 'empty') { |  | ||||||
|                 instant_remote_process(["rmdir '$parentFolder'"], $server); |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function render() |     public function render() | ||||||
|     { |     { | ||||||
|         return view('livewire.project.database.backup-edit', [ |         return view('livewire.project.database.backup-edit', [ | ||||||
|             'checkboxes' => [ |             'checkboxes' => [ | ||||||
|                 ['id' => 'delete_associated_backups_locally', 'label' => __('database.delete_backups_locally')], |                 ['id' => 'delete_associated_backups_locally', 'label' => __('database.delete_backups_locally')], | ||||||
|                 // ['id' => 'delete_associated_backups_s3', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected S3 Storage.']
 |                 ['id' => 'delete_associated_backups_s3', 'label' => 'All backups will be permanently deleted (associated with this backup job) from the selected S3 Storage.'], | ||||||
|                 // ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected SFTP Storage.']
 |                 // ['id' => 'delete_associated_backups_sftp', 'label' => 'All backups associated with this backup job from this database will be permanently deleted from the selected SFTP Storage.']
 | ||||||
|             ], |             ], | ||||||
|         ]); |         ]); | ||||||
|   | |||||||
| @@ -18,9 +18,9 @@ class BackupExecutions extends Component | |||||||
| 
 | 
 | ||||||
|     public $setDeletableBackup; |     public $setDeletableBackup; | ||||||
| 
 | 
 | ||||||
|     public $delete_backup_s3 = true; |     public $delete_backup_s3 = false; | ||||||
| 
 | 
 | ||||||
|     public $delete_backup_sftp = true; |     public $delete_backup_sftp = false; | ||||||
| 
 | 
 | ||||||
|     public function getListeners() |     public function getListeners() | ||||||
|     { |     { | ||||||
| @@ -57,23 +57,25 @@ class BackupExecutions extends Component | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         if ($execution->scheduledDatabaseBackup->database->getMorphClass() === \App\Models\ServiceDatabase::class) { |         $server = $execution->scheduledDatabaseBackup->database->getMorphClass() === \App\Models\ServiceDatabase::class | ||||||
|             delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->service->destination->server); |             ? $execution->scheduledDatabaseBackup->database->service->destination->server | ||||||
|         } else { |             : $execution->scheduledDatabaseBackup->database->destination->server; | ||||||
|             delete_backup_locally($execution->filename, $execution->scheduledDatabaseBackup->database->destination->server); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         if ($this->delete_backup_s3) { |         try { | ||||||
|             // Add logic to delete from S3
 |             if ($execution->filename) { | ||||||
|         } |                 deleteBackupsLocally($execution->filename, $server); | ||||||
| 
 | 
 | ||||||
|         if ($this->delete_backup_sftp) { |                 if ($this->delete_backup_s3 && $execution->scheduledDatabaseBackup->s3) { | ||||||
|             // Add logic to delete from SFTP
 |                     deleteBackupsS3($execution->filename, $execution->scheduledDatabaseBackup->s3); | ||||||
|         } |                 } | ||||||
|  |             } | ||||||
| 
 | 
 | ||||||
|         $execution->delete(); |             $execution->delete(); | ||||||
|         $this->dispatch('success', 'Backup deleted.'); |             $this->dispatch('success', 'Backup deleted.'); | ||||||
|         $this->refreshBackupExecutions(); |             $this->refreshBackupExecutions(); | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             $this->dispatch('error', 'Failed to delete backup: '.$e->getMessage()); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function download_file($exeuctionId) |     public function download_file($exeuctionId) | ||||||
| @@ -83,8 +85,10 @@ class BackupExecutions extends Component | |||||||
| 
 | 
 | ||||||
|     public function refreshBackupExecutions(): void |     public function refreshBackupExecutions(): void | ||||||
|     { |     { | ||||||
|         if ($this->backup) { |         if ($this->backup && $this->backup->exists) { | ||||||
|             $this->executions = $this->backup->executions()->get(); |             $this->executions = $this->backup->executions()->get()->toArray(); | ||||||
|  |         } else { | ||||||
|  |             $this->executions = []; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -141,7 +145,7 @@ class BackupExecutions extends Component | |||||||
|         return view('livewire.project.database.backup-executions', [ |         return view('livewire.project.database.backup-executions', [ | ||||||
|             'checkboxes' => [ |             'checkboxes' => [ | ||||||
|                 ['id' => 'delete_backup_s3', 'label' => 'Delete the selected backup permanently form S3 Storage'], |                 ['id' => 'delete_backup_s3', 'label' => 'Delete the selected backup permanently form S3 Storage'], | ||||||
|                 ['id' => 'delete_backup_sftp', 'label' => 'Delete the selected backup permanently form SFTP Storage'], |                 // ['id' => 'delete_backup_sftp', 'label' => 'Delete the selected backup permanently form SFTP Storage'],
 | ||||||
|             ], |             ], | ||||||
|         ]); |         ]); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -9,11 +9,9 @@ class BackupNow extends Component | |||||||
| { | { | ||||||
|     public $backup; |     public $backup; | ||||||
| 
 | 
 | ||||||
|     public function backup_now() |     public function backupNow() | ||||||
|     { |     { | ||||||
|         dispatch(new DatabaseBackupJob( |         DatabaseBackupJob::dispatch($this->backup); | ||||||
|             backup: $this->backup |  | ||||||
|         )); |  | ||||||
|         $this->dispatch('success', 'Backup queued. It will be available in a few minutes.'); |         $this->dispatch('success', 'Backup queued. It will be available in a few minutes.'); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -6,23 +6,34 @@ use Livewire\Component; | |||||||
| 
 | 
 | ||||||
| class Configuration extends Component | class Configuration extends Component | ||||||
| { | { | ||||||
|  |     public $currentRoute; | ||||||
|  | 
 | ||||||
|     public $database; |     public $database; | ||||||
| 
 | 
 | ||||||
|  |     public $project; | ||||||
|  | 
 | ||||||
|  |     public $environment; | ||||||
|  | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|         $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); |         $this->currentRoute = request()->route()->getName(); | ||||||
|         if (! $project) { | 
 | ||||||
|             return redirect()->route('dashboard'); |         $project = currentTeam() | ||||||
|         } |             ->projects() | ||||||
|         $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first()->load(['applications']); |             ->select('id', 'uuid', 'team_id') | ||||||
|         if (! $environment) { |             ->where('uuid', request()->route('project_uuid')) | ||||||
|             return redirect()->route('dashboard'); |             ->firstOrFail(); | ||||||
|         } |         $environment = $project->environments() | ||||||
|         $database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first(); |             ->select('id', 'name', 'project_id', 'uuid') | ||||||
|         if (! $database) { |             ->where('uuid', request()->route('environment_uuid')) | ||||||
|             return redirect()->route('dashboard'); |             ->firstOrFail(); | ||||||
|         } |         $database = $environment->databases() | ||||||
|  |             ->where('uuid', request()->route('database_uuid')) | ||||||
|  |             ->firstOrFail(); | ||||||
|  | 
 | ||||||
|         $this->database = $database; |         $this->database = $database; | ||||||
|  |         $this->project = $project; | ||||||
|  |         $this->environment = $environment; | ||||||
|         if (str($this->database->status)->startsWith('running') && is_null($this->database->config_hash)) { |         if (str($this->database->status)->startsWith('running') && is_null($this->database->config_hash)) { | ||||||
|             $this->database->isConfigurationChanged(true); |             $this->database->isConfigurationChanged(true); | ||||||
|             $this->dispatch('configurationChanged'); |             $this->dispatch('configurationChanged'); | ||||||
|   | |||||||
| @@ -37,6 +37,12 @@ class Import extends Component | |||||||
| 
 | 
 | ||||||
|     public array $importCommands = []; |     public array $importCommands = []; | ||||||
| 
 | 
 | ||||||
|  |     public bool $dumpAll = false; | ||||||
|  | 
 | ||||||
|  |     public string $restoreCommandText = ''; | ||||||
|  | 
 | ||||||
|  |     public string $customLocation = ''; | ||||||
|  | 
 | ||||||
|     public string $postgresqlRestoreCommand = 'pg_restore -U $POSTGRES_USER -d $POSTGRES_DB'; |     public string $postgresqlRestoreCommand = 'pg_restore -U $POSTGRES_USER -d $POSTGRES_DB'; | ||||||
| 
 | 
 | ||||||
|     public string $mysqlRestoreCommand = 'mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE'; |     public string $mysqlRestoreCommand = 'mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE'; | ||||||
| @@ -56,10 +62,62 @@ class Import extends Component | |||||||
| 
 | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|  |         if (isDev()) { | ||||||
|  |             $this->customLocation = '/data/coolify/pg-dump-all-1736245863.gz'; | ||||||
|  |         } | ||||||
|         $this->parameters = get_route_parameters(); |         $this->parameters = get_route_parameters(); | ||||||
|         $this->getContainers(); |         $this->getContainers(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function updatedDumpAll($value) | ||||||
|  |     { | ||||||
|  |         switch ($this->resource->getMorphClass()) { | ||||||
|  |             case \App\Models\StandaloneMariadb::class: | ||||||
|  |                 if ($value === true) { | ||||||
|  |                     $this->mariadbRestoreCommand = <<<'EOD' | ||||||
|  | for pid in $(mariadb -u root -p$MARIADB_ROOT_PASSWORD -N -e "SELECT id FROM information_schema.processlist WHERE user != 'root';"); do | ||||||
|  |   mariadb -u root -p$MARIADB_ROOT_PASSWORD -e "KILL $pid" 2>/dev/null || true | ||||||
|  | done && \ | ||||||
|  | mariadb -u root -p$MARIADB_ROOT_PASSWORD -N -e "SELECT CONCAT('DROP DATABASE IF EXISTS \`',schema_name,'\`;') FROM information_schema.schemata WHERE schema_name NOT IN ('information_schema','mysql','performance_schema','sys');" | mariadb -u root -p$MARIADB_ROOT_PASSWORD && \ | ||||||
|  | mariadb -u root -p$MARIADB_ROOT_PASSWORD -e "CREATE DATABASE IF NOT EXISTS \`default\`;" && \ | ||||||
|  | (gunzip -cf $tmpPath 2>/dev/null || cat $tmpPath) | sed -e '/^CREATE DATABASE/d' -e '/^USE \`mysql\`/d' | mariadb -u root -p$MARIADB_ROOT_PASSWORD default | ||||||
|  | EOD; | ||||||
|  |                     $this->restoreCommandText = $this->mariadbRestoreCommand.' && (gunzip -cf <temp_backup_file> 2>/dev/null || cat <temp_backup_file>) | mariadb -u root -p$MARIADB_ROOT_PASSWORD default'; | ||||||
|  |                 } else { | ||||||
|  |                     $this->mariadbRestoreCommand = 'mariadb -u $MARIADB_USER -p$MARIADB_PASSWORD $MARIADB_DATABASE'; | ||||||
|  |                 } | ||||||
|  |                 break; | ||||||
|  |             case \App\Models\StandaloneMysql::class: | ||||||
|  |                 if ($value === true) { | ||||||
|  |                     $this->mysqlRestoreCommand = <<<'EOD' | ||||||
|  | for pid in $(mysql -u root -p$MYSQL_ROOT_PASSWORD -N -e "SELECT id FROM information_schema.processlist WHERE user != 'root';"); do | ||||||
|  |   mysql -u root -p$MYSQL_ROOT_PASSWORD -e "KILL $pid" 2>/dev/null || true | ||||||
|  | done && \ | ||||||
|  | mysql -u root -p$MYSQL_ROOT_PASSWORD -N -e "SELECT CONCAT('DROP DATABASE IF EXISTS \`',schema_name,'\`;') FROM information_schema.schemata WHERE schema_name NOT IN ('information_schema','mysql','performance_schema','sys');" | mysql -u root -p$MYSQL_ROOT_PASSWORD && \ | ||||||
|  | mysql -u root -p$MYSQL_ROOT_PASSWORD -e "CREATE DATABASE IF NOT EXISTS \`default\`;" && \ | ||||||
|  | (gunzip -cf $tmpPath 2>/dev/null || cat $tmpPath) | sed -e '/^CREATE DATABASE/d' -e '/^USE \`mysql\`/d' | mysql -u root -p$MYSQL_ROOT_PASSWORD default | ||||||
|  | EOD; | ||||||
|  |                     $this->restoreCommandText = $this->mysqlRestoreCommand.' && (gunzip -cf <temp_backup_file> 2>/dev/null || cat <temp_backup_file>) | mysql -u root -p$MYSQL_ROOT_PASSWORD default'; | ||||||
|  |                 } else { | ||||||
|  |                     $this->mysqlRestoreCommand = 'mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE'; | ||||||
|  |                 } | ||||||
|  |                 break; | ||||||
|  |             case \App\Models\StandalonePostgresql::class: | ||||||
|  |                 if ($value === true) { | ||||||
|  |                     $this->postgresqlRestoreCommand = <<<'EOD' | ||||||
|  | psql -U $POSTGRES_USER -c "SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname IS NOT NULL AND pid <> pg_backend_pid()" && \ | ||||||
|  | psql -U $POSTGRES_USER -t -c "SELECT datname FROM pg_database WHERE NOT datistemplate" | xargs -I {} dropdb -U $POSTGRES_USER --if-exists {} && \ | ||||||
|  | createdb -U $POSTGRES_USER postgres | ||||||
|  | EOD; | ||||||
|  |                     $this->restoreCommandText = $this->postgresqlRestoreCommand.' && (gunzip -cf <temp_backup_file> 2>/dev/null || cat <temp_backup_file>) | psql -U $POSTGRES_USER postgres'; | ||||||
|  |                 } else { | ||||||
|  |                     $this->postgresqlRestoreCommand = 'pg_restore -U $POSTGRES_USER -d $POSTGRES_DB'; | ||||||
|  |                 } | ||||||
|  |                 break; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function getContainers() |     public function getContainers() | ||||||
|     { |     { | ||||||
|         $this->containers = collect(); |         $this->containers = collect(); | ||||||
| @@ -87,6 +145,24 @@ class Import extends Component | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function checkFile() | ||||||
|  |     { | ||||||
|  |         if (filled($this->customLocation)) { | ||||||
|  |             try { | ||||||
|  |                 $result = instant_remote_process(["ls -l {$this->customLocation}"], $this->server, throwError: false); | ||||||
|  |                 if (blank($result)) { | ||||||
|  |                     $this->dispatch('error', 'The file does not exist or has been deleted.'); | ||||||
|  | 
 | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |                 $this->filename = $this->customLocation; | ||||||
|  |                 $this->dispatch('success', 'The file exists.'); | ||||||
|  |             } catch (\Throwable $e) { | ||||||
|  |                 return handleError($e, $this); | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function runImport() |     public function runImport() | ||||||
|     { |     { | ||||||
|         if ($this->filename === '') { |         if ($this->filename === '') { | ||||||
| @@ -95,46 +171,83 @@ class Import extends Component | |||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
|         try { |         try { | ||||||
|             $uploadedFilename = "upload/{$this->resource->uuid}/restore"; |             $this->importCommands = []; | ||||||
|             $path = Storage::path($uploadedFilename); |             if (filled($this->customLocation)) { | ||||||
|             if (! Storage::exists($uploadedFilename)) { |                 $backupFileName = '/tmp/restore_'.$this->resource->uuid; | ||||||
|                 $this->dispatch('error', 'The file does not exist or has been deleted.'); |                 $this->importCommands[] = "docker cp {$this->customLocation} {$this->container}:{$backupFileName}"; | ||||||
|  |                 $tmpPath = $backupFileName; | ||||||
|  |             } else { | ||||||
|  |                 $backupFileName = "upload/{$this->resource->uuid}/restore"; | ||||||
|  |                 $path = Storage::path($backupFileName); | ||||||
|  |                 if (! Storage::exists($backupFileName)) { | ||||||
|  |                     $this->dispatch('error', 'The file does not exist or has been deleted.'); | ||||||
| 
 | 
 | ||||||
|                 return; |                     return; | ||||||
|  |                 } | ||||||
|  |                 $tmpPath = '/tmp/'.basename($backupFileName).'_'.$this->resource->uuid; | ||||||
|  |                 instant_scp($path, $tmpPath, $this->server); | ||||||
|  |                 Storage::delete($backupFileName); | ||||||
|  |                 $this->importCommands[] = "docker cp {$tmpPath} {$this->container}:{$tmpPath}"; | ||||||
|             } |             } | ||||||
|             $tmpPath = '/tmp/'.basename($uploadedFilename); | 
 | ||||||
|             instant_scp($path, $tmpPath, $this->server); |             // Copy the restore command to a script file
 | ||||||
|             Storage::delete($uploadedFilename); |             $scriptPath = "/tmp/restore_{$this->resource->uuid}.sh"; | ||||||
|             $this->importCommands[] = "docker cp {$tmpPath} {$this->container}:{$tmpPath}"; |  | ||||||
| 
 | 
 | ||||||
|             switch ($this->resource->getMorphClass()) { |             switch ($this->resource->getMorphClass()) { | ||||||
|                 case \App\Models\StandaloneMariadb::class: |                 case \App\Models\StandaloneMariadb::class: | ||||||
|                     $this->importCommands[] = "docker exec {$this->container} sh -c '{$this->mariadbRestoreCommand} < {$tmpPath}'"; |                     $restoreCommand = $this->mariadbRestoreCommand; | ||||||
|                     $this->importCommands[] = "rm {$tmpPath}"; |                     if ($this->dumpAll) { | ||||||
|  |                         $restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | mariadb -u root -p\$MARIADB_ROOT_PASSWORD"; | ||||||
|  |                     } else { | ||||||
|  |                         $restoreCommand .= " < {$tmpPath}"; | ||||||
|  |                     } | ||||||
|                     break; |                     break; | ||||||
|                 case \App\Models\StandaloneMysql::class: |                 case \App\Models\StandaloneMysql::class: | ||||||
|                     $this->importCommands[] = "docker exec {$this->container} sh -c '{$this->mysqlRestoreCommand} < {$tmpPath}'"; |                     $restoreCommand = $this->mysqlRestoreCommand; | ||||||
|                     $this->importCommands[] = "rm {$tmpPath}"; |                     if ($this->dumpAll) { | ||||||
|  |                         $restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | mysql -u root -p\$MYSQL_ROOT_PASSWORD"; | ||||||
|  |                     } else { | ||||||
|  |                         $restoreCommand .= " < {$tmpPath}"; | ||||||
|  |                     } | ||||||
|                     break; |                     break; | ||||||
|                 case \App\Models\StandalonePostgresql::class: |                 case \App\Models\StandalonePostgresql::class: | ||||||
|                     $this->importCommands[] = "docker exec {$this->container} sh -c '{$this->postgresqlRestoreCommand} {$tmpPath}'"; |                     $restoreCommand = $this->postgresqlRestoreCommand; | ||||||
|                     $this->importCommands[] = "rm {$tmpPath}"; |                     if ($this->dumpAll) { | ||||||
|  |                         $restoreCommand .= " && (gunzip -cf {$tmpPath} 2>/dev/null || cat {$tmpPath}) | psql -U \$POSTGRES_USER postgres"; | ||||||
|  |                     } else { | ||||||
|  |                         $restoreCommand .= " {$tmpPath}"; | ||||||
|  |                     } | ||||||
|                     break; |                     break; | ||||||
|                 case \App\Models\StandaloneMongodb::class: |                 case \App\Models\StandaloneMongodb::class: | ||||||
|                     $this->importCommands[] = "docker exec {$this->container} sh -c '{$this->mongodbRestoreCommand}{$tmpPath}'"; |                     $restoreCommand = $this->mongodbRestoreCommand; | ||||||
|                     $this->importCommands[] = "rm {$tmpPath}"; |                     if ($this->dumpAll === false) { | ||||||
|  |                         $restoreCommand .= " {$tmpPath}"; | ||||||
|  |                     } | ||||||
|                     break; |                     break; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             $this->importCommands[] = "docker exec {$this->container} sh -c 'rm {$tmpPath}'"; |             $restoreCommandBase64 = base64_encode($restoreCommand); | ||||||
|  |             $this->importCommands[] = "echo \"{$restoreCommandBase64}\" | base64 -d > {$scriptPath}"; | ||||||
|  |             $this->importCommands[] = "chmod +x {$scriptPath}"; | ||||||
|  |             $this->importCommands[] = "docker cp {$scriptPath} {$this->container}:{$scriptPath}"; | ||||||
|  | 
 | ||||||
|  |             $this->importCommands[] = "docker exec {$this->container} sh -c '{$scriptPath}'"; | ||||||
|             $this->importCommands[] = "docker exec {$this->container} sh -c 'echo \"Import finished with exit code $?\"'"; |             $this->importCommands[] = "docker exec {$this->container} sh -c 'echo \"Import finished with exit code $?\"'"; | ||||||
| 
 | 
 | ||||||
|             if (! empty($this->importCommands)) { |             if (! empty($this->importCommands)) { | ||||||
|                 $activity = remote_process($this->importCommands, $this->server, ignore_errors: true); |                 $activity = remote_process($this->importCommands, $this->server, ignore_errors: true, callEventOnFinish: 'RestoreJobFinished', callEventData: [ | ||||||
|  |                     'scriptPath' => $scriptPath, | ||||||
|  |                     'tmpPath' => $tmpPath, | ||||||
|  |                     'container' => $this->container, | ||||||
|  |                     'serverId' => $this->server->id, | ||||||
|  |                 ]); | ||||||
|                 $this->dispatch('activityMonitor', $activity->id); |                 $this->dispatch('activityMonitor', $activity->id); | ||||||
|             } |             } | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|  |         } finally { | ||||||
|  |             $this->filename = null; | ||||||
|  |             $this->importCommands = []; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -9,8 +9,6 @@ use App\Models\StandalonePostgresql; | |||||||
| use Exception; | use Exception; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
| 
 | 
 | ||||||
| use function Aws\filter; |  | ||||||
| 
 |  | ||||||
| class General extends Component | class General extends Component | ||||||
| { | { | ||||||
|     public StandalonePostgresql $database; |     public StandalonePostgresql $database; | ||||||
| @@ -126,10 +124,52 @@ class General extends Component | |||||||
| 
 | 
 | ||||||
|     public function save_init_script($script) |     public function save_init_script($script) | ||||||
|     { |     { | ||||||
|         $this->database->init_scripts = filter($this->database->init_scripts, fn ($s) => $s['filename'] !== $script['filename']); |         $initScripts = collect($this->database->init_scripts ?? []); | ||||||
|         $this->database->init_scripts = array_merge($this->database->init_scripts, [$script]); | 
 | ||||||
|  |         $existingScript = $initScripts->firstWhere('filename', $script['filename']); | ||||||
|  |         $oldScript = $initScripts->firstWhere('index', $script['index']); | ||||||
|  | 
 | ||||||
|  |         if ($existingScript && $existingScript['index'] !== $script['index']) { | ||||||
|  |             $this->dispatch('error', 'A script with this filename already exists.'); | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         $container_name = $this->database->uuid; | ||||||
|  |         $configuration_dir = database_configuration_dir().'/'.$container_name; | ||||||
|  | 
 | ||||||
|  |         if ($oldScript && $oldScript['filename'] !== $script['filename']) { | ||||||
|  |             $old_file_path = "$configuration_dir/docker-entrypoint-initdb.d/{$oldScript['filename']}"; | ||||||
|  |             $delete_command = "rm -f $old_file_path"; | ||||||
|  |             try { | ||||||
|  |                 instant_remote_process([$delete_command], $this->server); | ||||||
|  |             } catch (\Exception $e) { | ||||||
|  |                 $this->dispatch('error', 'Failed to remove old init script from server: '.$e->getMessage()); | ||||||
|  | 
 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         $index = $initScripts->search(function ($item) use ($script) { | ||||||
|  |             return $item['index'] === $script['index']; | ||||||
|  |         }); | ||||||
|  | 
 | ||||||
|  |         if ($index !== false) { | ||||||
|  |             $initScripts[$index] = $script; | ||||||
|  |         } else { | ||||||
|  |             $initScripts->push($script); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         $this->database->init_scripts = $initScripts->values() | ||||||
|  |             ->map(function ($item, $index) { | ||||||
|  |                 $item['index'] = $index; | ||||||
|  | 
 | ||||||
|  |                 return $item; | ||||||
|  |             }) | ||||||
|  |             ->all(); | ||||||
|  | 
 | ||||||
|         $this->database->save(); |         $this->database->save(); | ||||||
|         $this->dispatch('success', 'Init script saved.'); |         $this->dispatch('success', 'Init script saved and updated.'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function delete_init_script($script) |     public function delete_init_script($script) | ||||||
| @@ -137,12 +177,32 @@ class General extends Component | |||||||
|         $collection = collect($this->database->init_scripts); |         $collection = collect($this->database->init_scripts); | ||||||
|         $found = $collection->firstWhere('filename', $script['filename']); |         $found = $collection->firstWhere('filename', $script['filename']); | ||||||
|         if ($found) { |         if ($found) { | ||||||
|             $this->database->init_scripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename'])->toArray(); |             $container_name = $this->database->uuid; | ||||||
|  |             $configuration_dir = database_configuration_dir().'/'.$container_name; | ||||||
|  |             $file_path = "$configuration_dir/docker-entrypoint-initdb.d/{$script['filename']}"; | ||||||
|  | 
 | ||||||
|  |             $command = "rm -f $file_path"; | ||||||
|  |             try { | ||||||
|  |                 instant_remote_process([$command], $this->server); | ||||||
|  |             } catch (\Exception $e) { | ||||||
|  |                 $this->dispatch('error', 'Failed to remove init script from server: '.$e->getMessage()); | ||||||
|  | 
 | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $updatedScripts = $collection->filter(fn ($s) => $s['filename'] !== $script['filename']) | ||||||
|  |                 ->values() | ||||||
|  |                 ->map(function ($item, $index) { | ||||||
|  |                     $item['index'] = $index; | ||||||
|  | 
 | ||||||
|  |                     return $item; | ||||||
|  |                 }) | ||||||
|  |                 ->all(); | ||||||
|  | 
 | ||||||
|  |             $this->database->init_scripts = $updatedScripts; | ||||||
|             $this->database->save(); |             $this->database->save(); | ||||||
|             $this->refresh(); |             $this->refresh(); | ||||||
|             $this->dispatch('success', 'Init script deleted.'); |             $this->dispatch('success', 'Init script deleted from the database and the server.'); | ||||||
| 
 |  | ||||||
|             return; |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -23,11 +23,11 @@ class EnvironmentEdit extends Component | |||||||
|     #[Validate(['nullable', 'string', 'max:255'])]
 |     #[Validate(['nullable', 'string', 'max:255'])]
 | ||||||
|     public ?string $description = null; |     public ?string $description = null; | ||||||
| 
 | 
 | ||||||
|     public function mount(string $project_uuid, string $environment_name) |     public function mount(string $project_uuid, string $environment_uuid) | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $this->project = Project::ownedByCurrentTeam()->where('uuid', $project_uuid)->firstOrFail(); |             $this->project = Project::ownedByCurrentTeam()->where('uuid', $project_uuid)->firstOrFail(); | ||||||
|             $this->environment = $this->project->environments()->where('name', $environment_name)->firstOrFail(); |             $this->environment = $this->project->environments()->where('uuid', $environment_uuid)->firstOrFail(); | ||||||
|             $this->syncData(); |             $this->syncData(); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
| @@ -52,7 +52,10 @@ class EnvironmentEdit extends Component | |||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $this->syncData(true); |             $this->syncData(true); | ||||||
|             $this->redirectRoute('project.environment.edit', ['environment_name' => $this->environment->name, 'project_uuid' => $this->project->uuid]); |             $this->redirectRoute('project.environment.edit', [ | ||||||
|  |                 'environment_uuid' => $this->environment->uuid, | ||||||
|  |                 'project_uuid' => $this->project->uuid, | ||||||
|  |             ]); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ namespace App\Livewire\Project; | |||||||
| use App\Models\PrivateKey; | use App\Models\PrivateKey; | ||||||
| use App\Models\Project; | use App\Models\Project; | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
|  | use Illuminate\Support\Facades\Redirect; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
| 
 | 
 | ||||||
| class Index extends Component | class Index extends Component | ||||||
| @@ -30,4 +31,18 @@ class Index extends Component | |||||||
|     { |     { | ||||||
|         return view('livewire.project.index'); |         return view('livewire.project.index'); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function navigateToProject($projectUuid) | ||||||
|  |     { | ||||||
|  |         $project = Project::where('uuid', $projectUuid)->first(); | ||||||
|  | 
 | ||||||
|  |         if ($project && $project->environments->count() === 1) { | ||||||
|  |             return Redirect::route('project.resource.index', [ | ||||||
|  |                 'project_uuid' => $projectUuid, | ||||||
|  |                 'environment_uuid' => $project->environments->first()->uuid, | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return Redirect::route('project.show', ['project_uuid' => $projectUuid]); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -59,7 +59,7 @@ class DockerCompose extends Component | |||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); |             $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); | ||||||
|             $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); |             $environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first(); | ||||||
| 
 | 
 | ||||||
|             $destination_uuid = $this->query['destination']; |             $destination_uuid = $this->query['destination']; | ||||||
|             $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); |             $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); | ||||||
| @@ -87,7 +87,8 @@ class DockerCompose extends Component | |||||||
|                     'value' => $variable, |                     'value' => $variable, | ||||||
|                     'is_build_time' => false, |                     'is_build_time' => false, | ||||||
|                     'is_preview' => false, |                     'is_preview' => false, | ||||||
|                     'service_id' => $service->id, |                     'resourceable_id' => $service->id, | ||||||
|  |                     'resourceable_type' => $service->getMorphClass(), | ||||||
|                 ]); |                 ]); | ||||||
|             } |             } | ||||||
|             $service->name = "service-$service->uuid"; |             $service->name = "service-$service->uuid"; | ||||||
| @@ -96,7 +97,7 @@ class DockerCompose extends Component | |||||||
| 
 | 
 | ||||||
|             return redirect()->route('project.service.configuration', [ |             return redirect()->route('project.service.configuration', [ | ||||||
|                 'service_uuid' => $service->uuid, |                 'service_uuid' => $service->uuid, | ||||||
|                 'environment_name' => $environment->name, |                 'environment_uuid' => $environment->uuid, | ||||||
|                 'project_uuid' => $project->uuid, |                 'project_uuid' => $project->uuid, | ||||||
|             ]); |             ]); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ class DockerImage extends Component | |||||||
|         $destination_class = $destination->getMorphClass(); |         $destination_class = $destination->getMorphClass(); | ||||||
| 
 | 
 | ||||||
|         $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); |         $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); | ||||||
|         $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); |         $environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first(); | ||||||
|         $application = Application::create([ |         $application = Application::create([ | ||||||
|             'name' => 'docker-image-'.new Cuid2, |             'name' => 'docker-image-'.new Cuid2, | ||||||
|             'repository_project_id' => 0, |             'repository_project_id' => 0, | ||||||
| @@ -69,7 +69,7 @@ class DockerImage extends Component | |||||||
| 
 | 
 | ||||||
|         return redirect()->route('project.application.configuration', [ |         return redirect()->route('project.application.configuration', [ | ||||||
|             'application_uuid' => $application->uuid, |             'application_uuid' => $application->uuid, | ||||||
|             'environment_name' => $environment->name, |             'environment_uuid' => $environment->uuid, | ||||||
|             'project_uuid' => $project->uuid, |             'project_uuid' => $project->uuid, | ||||||
|         ]); |         ]); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ namespace App\Livewire\Project\New; | |||||||
| 
 | 
 | ||||||
| use App\Models\Project; | use App\Models\Project; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
|  | use Visus\Cuid2\Cuid2; | ||||||
| 
 | 
 | ||||||
| class EmptyProject extends Component | class EmptyProject extends Component | ||||||
| { | { | ||||||
| @@ -12,8 +13,9 @@ class EmptyProject extends Component | |||||||
|         $project = Project::create([ |         $project = Project::create([ | ||||||
|             'name' => generate_random_name(), |             'name' => generate_random_name(), | ||||||
|             'team_id' => currentTeam()->id, |             'team_id' => currentTeam()->id, | ||||||
|  |             'uuid' => (string) new Cuid2, | ||||||
|         ]); |         ]); | ||||||
| 
 | 
 | ||||||
|         return redirect()->route('project.show', ['project_uuid' => $project->uuid, 'environment_name' => 'production']); |         return redirect()->route('project.show', ['project_uuid' => $project->uuid, 'environment_uuid' => $project->environments->first()->uuid]); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -177,7 +177,7 @@ class GithubPrivateRepository extends Component | |||||||
|             $destination_class = $destination->getMorphClass(); |             $destination_class = $destination->getMorphClass(); | ||||||
| 
 | 
 | ||||||
|             $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); |             $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); | ||||||
|             $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); |             $environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first(); | ||||||
| 
 | 
 | ||||||
|             $application = Application::create([ |             $application = Application::create([ | ||||||
|                 'name' => generate_application_name($this->selected_repository_owner.'/'.$this->selected_repository_repo, $this->selected_branch_name), |                 'name' => generate_application_name($this->selected_repository_owner.'/'.$this->selected_repository_repo, $this->selected_branch_name), | ||||||
| @@ -211,7 +211,7 @@ class GithubPrivateRepository extends Component | |||||||
| 
 | 
 | ||||||
|             return redirect()->route('project.application.configuration', [ |             return redirect()->route('project.application.configuration', [ | ||||||
|                 'application_uuid' => $application->uuid, |                 'application_uuid' => $application->uuid, | ||||||
|                 'environment_name' => $environment->name, |                 'environment_uuid' => $environment->uuid, | ||||||
|                 'project_uuid' => $project->uuid, |                 'project_uuid' => $project->uuid, | ||||||
|             ]); |             ]); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|   | |||||||
| @@ -136,7 +136,7 @@ class GithubPrivateRepositoryDeployKey extends Component | |||||||
|             $this->get_git_source(); |             $this->get_git_source(); | ||||||
| 
 | 
 | ||||||
|             $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); |             $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); | ||||||
|             $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); |             $environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first(); | ||||||
|             if ($this->git_source === 'other') { |             if ($this->git_source === 'other') { | ||||||
|                 $application_init = [ |                 $application_init = [ | ||||||
|                     'name' => generate_random_name(), |                     'name' => generate_random_name(), | ||||||
| @@ -184,7 +184,7 @@ class GithubPrivateRepositoryDeployKey extends Component | |||||||
| 
 | 
 | ||||||
|             return redirect()->route('project.application.configuration', [ |             return redirect()->route('project.application.configuration', [ | ||||||
|                 'application_uuid' => $application->uuid, |                 'application_uuid' => $application->uuid, | ||||||
|                 'environment_name' => $environment->name, |                 'environment_uuid' => $environment->uuid, | ||||||
|                 'project_uuid' => $project->uuid, |                 'project_uuid' => $project->uuid, | ||||||
|             ]); |             ]); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|   | |||||||
| @@ -225,7 +225,7 @@ class PublicGitRepository extends Component | |||||||
|             $this->validate(); |             $this->validate(); | ||||||
|             $destination_uuid = $this->query['destination']; |             $destination_uuid = $this->query['destination']; | ||||||
|             $project_uuid = $this->parameters['project_uuid']; |             $project_uuid = $this->parameters['project_uuid']; | ||||||
|             $environment_name = $this->parameters['environment_name']; |             $environment_uuid = $this->parameters['environment_uuid']; | ||||||
| 
 | 
 | ||||||
|             $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); |             $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); | ||||||
|             if (! $destination) { |             if (! $destination) { | ||||||
| @@ -237,7 +237,7 @@ class PublicGitRepository extends Component | |||||||
|             $destination_class = $destination->getMorphClass(); |             $destination_class = $destination->getMorphClass(); | ||||||
| 
 | 
 | ||||||
|             $project = Project::where('uuid', $project_uuid)->first(); |             $project = Project::where('uuid', $project_uuid)->first(); | ||||||
|             $environment = $project->load(['environments'])->environments->where('name', $environment_name)->first(); |             $environment = $project->load(['environments'])->environments->where('uuid', $environment_uuid)->first(); | ||||||
| 
 | 
 | ||||||
|             if ($this->build_pack === 'dockercompose' && isDev() && $this->new_compose_services) { |             if ($this->build_pack === 'dockercompose' && isDev() && $this->new_compose_services) { | ||||||
|                 $server = $destination->server; |                 $server = $destination->server; | ||||||
| @@ -260,7 +260,7 @@ class PublicGitRepository extends Component | |||||||
| 
 | 
 | ||||||
|                 return redirect()->route('project.service.configuration', [ |                 return redirect()->route('project.service.configuration', [ | ||||||
|                     'service_uuid' => $service->uuid, |                     'service_uuid' => $service->uuid, | ||||||
|                     'environment_name' => $environment->name, |                     'environment_uuid' => $environment->uuid, | ||||||
|                     'project_uuid' => $project->uuid, |                     'project_uuid' => $project->uuid, | ||||||
|                 ]); |                 ]); | ||||||
| 
 | 
 | ||||||
| @@ -319,7 +319,7 @@ class PublicGitRepository extends Component | |||||||
| 
 | 
 | ||||||
|             return redirect()->route('project.application.configuration', [ |             return redirect()->route('project.application.configuration', [ | ||||||
|                 'application_uuid' => $application->uuid, |                 'application_uuid' => $application->uuid, | ||||||
|                 'environment_name' => $environment->name, |                 'environment_uuid' => $environment->uuid, | ||||||
|                 'project_uuid' => $project->uuid, |                 'project_uuid' => $project->uuid, | ||||||
|             ]); |             ]); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|   | |||||||
| @@ -23,6 +23,8 @@ class Select extends Component | |||||||
| 
 | 
 | ||||||
|     public Collection|null|Server $servers; |     public Collection|null|Server $servers; | ||||||
| 
 | 
 | ||||||
|  |     public bool $onlyBuildServerAvailable = false; | ||||||
|  | 
 | ||||||
|     public ?Collection $standaloneDockers; |     public ?Collection $standaloneDockers; | ||||||
| 
 | 
 | ||||||
|     public ?Collection $swarmDockers; |     public ?Collection $swarmDockers; | ||||||
| @@ -61,7 +63,7 @@ class Select extends Component | |||||||
|         } |         } | ||||||
|         $projectUuid = data_get($this->parameters, 'project_uuid'); |         $projectUuid = data_get($this->parameters, 'project_uuid'); | ||||||
|         $this->environments = Project::whereUuid($projectUuid)->first()->environments; |         $this->environments = Project::whereUuid($projectUuid)->first()->environments; | ||||||
|         $this->selectedEnvironment = data_get($this->parameters, 'environment_name'); |         $this->selectedEnvironment = data_get($this->parameters, 'environment_uuid'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function render() |     public function render() | ||||||
| @@ -73,20 +75,10 @@ class Select extends Component | |||||||
|     { |     { | ||||||
|         return redirect()->route('project.resource.create', [ |         return redirect()->route('project.resource.create', [ | ||||||
|             'project_uuid' => $this->parameters['project_uuid'], |             'project_uuid' => $this->parameters['project_uuid'], | ||||||
|             'environment_name' => $this->selectedEnvironment, |             'environment_uuid' => $this->selectedEnvironment, | ||||||
|         ]); |         ]); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // public function addExistingPostgresql()
 |  | ||||||
|     // {
 |  | ||||||
|     //     try {
 |  | ||||||
|     //         instantCommand("psql {$this->existingPostgresqlUrl} -c 'SELECT 1'");
 |  | ||||||
|     //         $this->dispatch('success', 'Successfully connected to the database.');
 |  | ||||||
|     //     } catch (\Throwable $e) {
 |  | ||||||
|     //         return handleError($e, $this);
 |  | ||||||
|     //     }
 |  | ||||||
|     // }
 |  | ||||||
| 
 |  | ||||||
|     public function loadServices() |     public function loadServices() | ||||||
|     { |     { | ||||||
|         $services = get_service_templates(true); |         $services = get_service_templates(true); | ||||||
| @@ -308,7 +300,7 @@ class Select extends Component | |||||||
| 
 | 
 | ||||||
|         return redirect()->route('project.resource.create', [ |         return redirect()->route('project.resource.create', [ | ||||||
|             'project_uuid' => $this->parameters['project_uuid'], |             'project_uuid' => $this->parameters['project_uuid'], | ||||||
|             'environment_name' => $this->parameters['environment_name'], |             'environment_uuid' => $this->parameters['environment_uuid'], | ||||||
|             'type' => $this->type, |             'type' => $this->type, | ||||||
|             'destination' => $this->destination_uuid, |             'destination' => $this->destination_uuid, | ||||||
|             'server_id' => $this->server_id, |             'server_id' => $this->server_id, | ||||||
| @@ -323,7 +315,7 @@ class Select extends Component | |||||||
|         } else { |         } else { | ||||||
|             return redirect()->route('project.resource.create', [ |             return redirect()->route('project.resource.create', [ | ||||||
|                 'project_uuid' => $this->parameters['project_uuid'], |                 'project_uuid' => $this->parameters['project_uuid'], | ||||||
|                 'environment_name' => $this->parameters['environment_name'], |                 'environment_uuid' => $this->parameters['environment_uuid'], | ||||||
|                 'type' => $this->type, |                 'type' => $this->type, | ||||||
|                 'destination' => $this->destination_uuid, |                 'destination' => $this->destination_uuid, | ||||||
|                 'server_id' => $this->server_id, |                 'server_id' => $this->server_id, | ||||||
| @@ -335,5 +327,11 @@ class Select extends Component | |||||||
|     { |     { | ||||||
|         $this->servers = Server::isUsable()->get()->sortBy('name'); |         $this->servers = Server::isUsable()->get()->sortBy('name'); | ||||||
|         $this->allServers = $this->servers; |         $this->allServers = $this->servers; | ||||||
|  | 
 | ||||||
|  |         if ($this->allServers && $this->allServers->isNotEmpty()) { | ||||||
|  |             $this->onlyBuildServerAvailable = $this->allServers->every(function ($server) { | ||||||
|  |                 return $server->isBuildServer(); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -46,7 +46,7 @@ CMD ["nginx", "-g", "daemon off;"] | |||||||
|         $destination_class = $destination->getMorphClass(); |         $destination_class = $destination->getMorphClass(); | ||||||
| 
 | 
 | ||||||
|         $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); |         $project = Project::where('uuid', $this->parameters['project_uuid'])->first(); | ||||||
|         $environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first(); |         $environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first(); | ||||||
| 
 | 
 | ||||||
|         $port = get_port_from_dockerfile($this->dockerfile); |         $port = get_port_from_dockerfile($this->dockerfile); | ||||||
|         if (! $port) { |         if (! $port) { | ||||||
| @@ -78,7 +78,7 @@ CMD ["nginx", "-g", "daemon off;"] | |||||||
| 
 | 
 | ||||||
|         return redirect()->route('project.application.configuration', [ |         return redirect()->route('project.application.configuration', [ | ||||||
|             'application_uuid' => $application->uuid, |             'application_uuid' => $application->uuid, | ||||||
|             'environment_name' => $environment->name, |             'environment_uuid' => $environment->uuid, | ||||||
|             'project_uuid' => $project->uuid, |             'project_uuid' => $project->uuid, | ||||||
|         ]); |         ]); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -25,7 +25,7 @@ class Create extends Component | |||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
|         $this->project = $project; |         $this->project = $project; | ||||||
|         $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first(); |         $environment = $project->load(['environments'])->environments->where('uuid', request()->route('environment_uuid'))->first(); | ||||||
|         if (! $environment) { |         if (! $environment) { | ||||||
|             return redirect()->route('dashboard'); |             return redirect()->route('dashboard'); | ||||||
|         } |         } | ||||||
| @@ -57,7 +57,7 @@ class Create extends Component | |||||||
| 
 | 
 | ||||||
|                 return redirect()->route('project.database.configuration', [ |                 return redirect()->route('project.database.configuration', [ | ||||||
|                     'project_uuid' => $project->uuid, |                     'project_uuid' => $project->uuid, | ||||||
|                     'environment_name' => $environment->name, |                     'environment_uuid' => $environment->uuid, | ||||||
|                     'database_uuid' => $database->uuid, |                     'database_uuid' => $database->uuid, | ||||||
|                 ]); |                 ]); | ||||||
|             } |             } | ||||||
| @@ -95,7 +95,8 @@ class Create extends Component | |||||||
|                                 EnvironmentVariable::create([ |                                 EnvironmentVariable::create([ | ||||||
|                                     'key' => $key, |                                     'key' => $key, | ||||||
|                                     'value' => $value, |                                     'value' => $value, | ||||||
|                                     'service_id' => $service->id, |                                     'resourceable_id' => $service->id, | ||||||
|  |                                     'resourceable_type' => $service->getMorphClass(), | ||||||
|                                     'is_build_time' => false, |                                     'is_build_time' => false, | ||||||
|                                     'is_preview' => false, |                                     'is_preview' => false, | ||||||
|                                 ]); |                                 ]); | ||||||
| @@ -106,7 +107,7 @@ class Create extends Component | |||||||
| 
 | 
 | ||||||
|                     return redirect()->route('project.service.configuration', [ |                     return redirect()->route('project.service.configuration', [ | ||||||
|                         'service_uuid' => $service->uuid, |                         'service_uuid' => $service->uuid, | ||||||
|                         'environment_name' => $environment->name, |                         'environment_uuid' => $environment->uuid, | ||||||
|                         'project_uuid' => $project->uuid, |                         'project_uuid' => $project->uuid, | ||||||
|                     ]); |                     ]); | ||||||
|                 } |                 } | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ class EnvironmentSelect extends Component | |||||||
| 
 | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|         $this->selectedEnvironment = request()->route('environment_name'); |         $this->selectedEnvironment = request()->route('environment_uuid'); | ||||||
|         $this->project_uuid = request()->route('project_uuid'); |         $this->project_uuid = request()->route('project_uuid'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -28,7 +28,7 @@ class EnvironmentSelect extends Component | |||||||
|         } else { |         } else { | ||||||
|             return redirect()->route('project.resource.index', [ |             return redirect()->route('project.resource.index', [ | ||||||
|                 'project_uuid' => $this->project_uuid, |                 'project_uuid' => $this->project_uuid, | ||||||
|                 'environment_name' => $value, |                 'environment_uuid' => $value, | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ namespace App\Livewire\Project\Resource; | |||||||
| 
 | 
 | ||||||
| use App\Models\Environment; | use App\Models\Environment; | ||||||
| use App\Models\Project; | use App\Models\Project; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
| 
 | 
 | ||||||
| class Index extends Component | class Index extends Component | ||||||
| @@ -12,39 +13,42 @@ class Index extends Component | |||||||
| 
 | 
 | ||||||
|     public Environment $environment; |     public Environment $environment; | ||||||
| 
 | 
 | ||||||
|     public $applications = []; |     public Collection $applications; | ||||||
| 
 | 
 | ||||||
|     public $postgresqls = []; |     public Collection $postgresqls; | ||||||
| 
 | 
 | ||||||
|     public $redis = []; |     public Collection $redis; | ||||||
| 
 | 
 | ||||||
|     public $mongodbs = []; |     public Collection $mongodbs; | ||||||
| 
 | 
 | ||||||
|     public $mysqls = []; |     public Collection $mysqls; | ||||||
| 
 | 
 | ||||||
|     public $mariadbs = []; |     public Collection $mariadbs; | ||||||
| 
 | 
 | ||||||
|     public $keydbs = []; |     public Collection $keydbs; | ||||||
| 
 | 
 | ||||||
|     public $dragonflies = []; |     public Collection $dragonflies; | ||||||
| 
 | 
 | ||||||
|     public $clickhouses = []; |     public Collection $clickhouses; | ||||||
| 
 | 
 | ||||||
|     public $services = []; |     public Collection $services; | ||||||
| 
 | 
 | ||||||
|     public array $parameters; |     public array $parameters; | ||||||
| 
 | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|  |         $this->applications = $this->postgresqls = $this->redis = $this->mongodbs = $this->mysqls = $this->mariadbs = $this->keydbs = $this->dragonflies = $this->clickhouses = $this->services = collect(); | ||||||
|         $this->parameters = get_route_parameters(); |         $this->parameters = get_route_parameters(); | ||||||
|         $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); |         $project = currentTeam() | ||||||
|         if (! $project) { |             ->projects() | ||||||
|             return redirect()->route('dashboard'); |             ->select('id', 'uuid', 'team_id', 'name') | ||||||
|         } |             ->where('uuid', request()->route('project_uuid')) | ||||||
|         $environment = $project->load(['environments'])->environments->where('name', request()->route('environment_name'))->first(); |             ->firstOrFail(); | ||||||
|         if (! $environment) { |         $environment = $project->environments() | ||||||
|             return redirect()->route('dashboard'); |             ->select('id', 'uuid', 'name', 'project_id') | ||||||
|         } |             ->where('uuid', request()->route('environment_uuid')) | ||||||
|  |             ->firstOrFail(); | ||||||
|  | 
 | ||||||
|         $this->project = $project; |         $this->project = $project; | ||||||
|         $this->environment = $environment->loadCount([ |         $this->environment = $environment->loadCount([ | ||||||
|             'applications', |             'applications', | ||||||
| @@ -69,9 +73,9 @@ class Index extends Component | |||||||
|         ])->get()->sortBy('name'); |         ])->get()->sortBy('name'); | ||||||
|         $this->applications = $this->applications->map(function ($application) { |         $this->applications = $this->applications->map(function ($application) { | ||||||
|             $application->hrefLink = route('project.application.configuration', [ |             $application->hrefLink = route('project.application.configuration', [ | ||||||
|                 'project_uuid' => $this->project->uuid, |                 'project_uuid' => data_get($application, 'environment.project.uuid'), | ||||||
|                 'application_uuid' => $application->uuid, |                 'environment_uuid' => data_get($application, 'environment.uuid'), | ||||||
|                 'environment_name' => $this->environment->name, |                 'application_uuid' => data_get($application, 'uuid'), | ||||||
|             ]); |             ]); | ||||||
| 
 | 
 | ||||||
|             return $application; |             return $application; | ||||||
| @@ -89,14 +93,6 @@ class Index extends Component | |||||||
|             'clickhouses' => 'clickhouses', |             'clickhouses' => 'clickhouses', | ||||||
|         ]; |         ]; | ||||||
| 
 | 
 | ||||||
|         // Load all server-related data first to prevent duplicate queries
 |  | ||||||
|         $serverData = $this->environment->applications() |  | ||||||
|             ->with(['destination.server.settings']) |  | ||||||
|             ->get() |  | ||||||
|             ->pluck('destination.server') |  | ||||||
|             ->filter() |  | ||||||
|             ->unique('id'); |  | ||||||
| 
 |  | ||||||
|         foreach ($databaseTypes as $property => $relation) { |         foreach ($databaseTypes as $property => $relation) { | ||||||
|             $this->{$property} = $this->environment->{$relation}()->with([ |             $this->{$property} = $this->environment->{$relation}()->with([ | ||||||
|                 'tags', |                 'tags', | ||||||
| @@ -106,7 +102,7 @@ class Index extends Component | |||||||
|                 $db->hrefLink = route('project.database.configuration', [ |                 $db->hrefLink = route('project.database.configuration', [ | ||||||
|                     'project_uuid' => $this->project->uuid, |                     'project_uuid' => $this->project->uuid, | ||||||
|                     'database_uuid' => $db->uuid, |                     'database_uuid' => $db->uuid, | ||||||
|                     'environment_name' => $this->environment->name, |                     'environment_uuid' => data_get($this->environment, 'uuid'), | ||||||
|                 ]); |                 ]); | ||||||
| 
 | 
 | ||||||
|                 return $db; |                 return $db; | ||||||
| @@ -120,9 +116,9 @@ class Index extends Component | |||||||
|         ])->get()->sortBy('name'); |         ])->get()->sortBy('name'); | ||||||
|         $this->services = $this->services->map(function ($service) { |         $this->services = $this->services->map(function ($service) { | ||||||
|             $service->hrefLink = route('project.service.configuration', [ |             $service->hrefLink = route('project.service.configuration', [ | ||||||
|                 'project_uuid' => $this->project->uuid, |                 'project_uuid' => data_get($service, 'environment.project.uuid'), | ||||||
|                 'service_uuid' => $service->uuid, |                 'environment_uuid' => data_get($service, 'environment.uuid'), | ||||||
|                 'environment_name' => $this->environment->name, |                 'service_uuid' => data_get($service, 'uuid'), | ||||||
|             ]); |             ]); | ||||||
| 
 | 
 | ||||||
|             return $service; |             return $service; | ||||||
|   | |||||||
| @@ -9,16 +9,22 @@ use Livewire\Component; | |||||||
| 
 | 
 | ||||||
| class Configuration extends Component | class Configuration extends Component | ||||||
| { | { | ||||||
|  |     public $currentRoute; | ||||||
|  | 
 | ||||||
|  |     public $project; | ||||||
|  | 
 | ||||||
|  |     public $environment; | ||||||
|  | 
 | ||||||
|     public ?Service $service = null; |     public ?Service $service = null; | ||||||
| 
 | 
 | ||||||
|     public $applications; |     public $applications; | ||||||
| 
 | 
 | ||||||
|     public $databases; |     public $databases; | ||||||
| 
 | 
 | ||||||
|     public array $parameters; |  | ||||||
| 
 |  | ||||||
|     public array $query; |     public array $query; | ||||||
| 
 | 
 | ||||||
|  |     public array $parameters; | ||||||
|  | 
 | ||||||
|     public function getListeners() |     public function getListeners() | ||||||
|     { |     { | ||||||
|         $userId = Auth::id(); |         $userId = Auth::id(); | ||||||
| @@ -38,11 +44,21 @@ class Configuration extends Component | |||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|         $this->parameters = get_route_parameters(); |         $this->parameters = get_route_parameters(); | ||||||
|  |         $this->currentRoute = request()->route()->getName(); | ||||||
|         $this->query = request()->query(); |         $this->query = request()->query(); | ||||||
|         $this->service = Service::whereUuid($this->parameters['service_uuid'])->first(); |         $project = currentTeam() | ||||||
|         if (! $this->service) { |             ->projects() | ||||||
|             return redirect()->route('dashboard'); |             ->select('id', 'uuid', 'team_id') | ||||||
|         } |             ->where('uuid', request()->route('project_uuid')) | ||||||
|  |             ->firstOrFail(); | ||||||
|  |         $environment = $project->environments() | ||||||
|  |             ->select('id', 'uuid', 'name', 'project_id') | ||||||
|  |             ->where('uuid', request()->route('environment_uuid')) | ||||||
|  |             ->firstOrFail(); | ||||||
|  |         $this->service = $environment->services()->whereUuid(request()->route('service_uuid'))->firstOrFail(); | ||||||
|  | 
 | ||||||
|  |         $this->project = $project; | ||||||
|  |         $this->environment = $environment; | ||||||
|         $this->applications = $this->service->applications->sort(); |         $this->applications = $this->service->applications->sort(); | ||||||
|         $this->databases = $this->service->databases->sort(); |         $this->databases = $this->service->databases->sort(); | ||||||
|     } |     } | ||||||
| @@ -83,7 +99,7 @@ class Configuration extends Component | |||||||
|             $this->service->databases->each(function ($database) { |             $this->service->databases->each(function ($database) { | ||||||
|                 $database->refresh(); |                 $database->refresh(); | ||||||
|             }); |             }); | ||||||
|             $this->dispatch('$refresh'); |             $this->dispatch('refresh'); | ||||||
|         } catch (\Exception $e) { |         } catch (\Exception $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -5,6 +5,7 @@ namespace App\Livewire\Project\Service; | |||||||
| use App\Actions\Service\StartService; | use App\Actions\Service\StartService; | ||||||
| use App\Actions\Service\StopService; | use App\Actions\Service\StopService; | ||||||
| use App\Actions\Shared\PullImage; | use App\Actions\Shared\PullImage; | ||||||
|  | use App\Enums\ProcessStatus; | ||||||
| use App\Events\ServiceStatusChanged; | use App\Events\ServiceStatusChanged; | ||||||
| use App\Models\Service; | use App\Models\Service; | ||||||
| use Illuminate\Support\Facades\Auth; | use Illuminate\Support\Facades\Auth; | ||||||
| @@ -68,11 +69,9 @@ class Navbar extends Component | |||||||
|     public function checkDeployments() |     public function checkDeployments() | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             // TODO: This is a temporary solution. We need to refactor this.
 |  | ||||||
|             // We need to delete null bytes somehow.
 |  | ||||||
|             $activity = Activity::where('properties->type_uuid', $this->service->uuid)->latest()->first(); |             $activity = Activity::where('properties->type_uuid', $this->service->uuid)->latest()->first(); | ||||||
|             $status = data_get($activity, 'properties.status'); |             $status = data_get($activity, 'properties.status'); | ||||||
|             if ($status === 'queued' || $status === 'in_progress') { |             if ($status === ProcessStatus::QUEUED->value || $status === ProcessStatus::IN_PROGRESS->value) { | ||||||
|                 $this->isDeploymentProgress = true; |                 $this->isDeploymentProgress = true; | ||||||
|             } else { |             } else { | ||||||
|                 $this->isDeploymentProgress = false; |                 $this->isDeploymentProgress = false; | ||||||
| @@ -80,25 +79,46 @@ class Navbar extends Component | |||||||
|         } catch (\Throwable) { |         } catch (\Throwable) { | ||||||
|             $this->isDeploymentProgress = false; |             $this->isDeploymentProgress = false; | ||||||
|         } |         } | ||||||
|  | 
 | ||||||
|  |         return $this->isDeploymentProgress; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function start() |     public function start() | ||||||
|     { |     { | ||||||
|         $this->checkDeployments(); |  | ||||||
|         if ($this->isDeploymentProgress) { |  | ||||||
|             $this->dispatch('error', 'There is a deployment in progress.'); |  | ||||||
| 
 |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         $this->service->parse(); |         $this->service->parse(); | ||||||
|         $activity = StartService::run($this->service); |         $activity = StartService::run($this->service); | ||||||
|         $this->dispatch('activityMonitor', $activity->id); |         $this->dispatch('activityMonitor', $activity->id); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function stop() |     public function forceDeploy() | ||||||
|     { |     { | ||||||
|         StopService::run($this->service, false, $this->docker_cleanup); |         try { | ||||||
|         ServiceStatusChanged::dispatch(); |             $activities = Activity::where('properties->type_uuid', $this->service->uuid)->where('properties->status', ProcessStatus::IN_PROGRESS->value)->orWhere('properties->status', ProcessStatus::QUEUED->value)->get(); | ||||||
|  |             foreach ($activities as $activity) { | ||||||
|  |                 $activity->properties->status = ProcessStatus::ERROR->value; | ||||||
|  |                 $activity->save(); | ||||||
|  |             } | ||||||
|  |             $this->service->parse(); | ||||||
|  |             $activity = StartService::run($this->service); | ||||||
|  |             $this->dispatch('activityMonitor', $activity->id); | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             $this->dispatch('error', $e->getMessage()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function stop($cleanupContainers = false) | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             StopService::run($this->service, false, $this->docker_cleanup); | ||||||
|  |             ServiceStatusChanged::dispatch(); | ||||||
|  |             if ($cleanupContainers) { | ||||||
|  |                 $this->dispatch('success', 'Containers cleaned up.'); | ||||||
|  |             } else { | ||||||
|  |                 $this->dispatch('success', 'Service stopped.'); | ||||||
|  |             } | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             $this->dispatch('error', $e->getMessage()); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function restart() |     public function restart() | ||||||
|   | |||||||
| @@ -20,7 +20,7 @@ class Danger extends Component | |||||||
| 
 | 
 | ||||||
|     public $projectUuid; |     public $projectUuid; | ||||||
| 
 | 
 | ||||||
|     public $environmentName; |     public $environmentUuid; | ||||||
| 
 | 
 | ||||||
|     public bool $delete_configurations = true; |     public bool $delete_configurations = true; | ||||||
| 
 | 
 | ||||||
| @@ -39,7 +39,7 @@ class Danger extends Component | |||||||
|         $parameters = get_route_parameters(); |         $parameters = get_route_parameters(); | ||||||
|         $this->modalId = new Cuid2; |         $this->modalId = new Cuid2; | ||||||
|         $this->projectUuid = data_get($parameters, 'project_uuid'); |         $this->projectUuid = data_get($parameters, 'project_uuid'); | ||||||
|         $this->environmentName = data_get($parameters, 'environment_name'); |         $this->environmentUuid = data_get($parameters, 'environment_uuid'); | ||||||
| 
 | 
 | ||||||
|         if ($this->resource === null) { |         if ($this->resource === null) { | ||||||
|             if (isset($parameters['service_uuid'])) { |             if (isset($parameters['service_uuid'])) { | ||||||
| @@ -107,7 +107,7 @@ class Danger extends Component | |||||||
| 
 | 
 | ||||||
|             return redirect()->route('project.resource.index', [ |             return redirect()->route('project.resource.index', [ | ||||||
|                 'project_uuid' => $this->projectUuid, |                 'project_uuid' => $this->projectUuid, | ||||||
|                 'environment_name' => $this->environmentName, |                 'environment_uuid' => $this->environmentUuid, | ||||||
|             ]); |             ]); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             return handleError($e, $this); |             return handleError($e, $this); | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ use App\Events\ApplicationStatusChanged; | |||||||
| use App\Models\InstanceSettings; | use App\Models\InstanceSettings; | ||||||
| use App\Models\Server; | use App\Models\Server; | ||||||
| use App\Models\StandaloneDocker; | use App\Models\StandaloneDocker; | ||||||
|  | use Illuminate\Support\Collection; | ||||||
| use Illuminate\Support\Facades\Auth; | use Illuminate\Support\Facades\Auth; | ||||||
| use Illuminate\Support\Facades\Hash; | use Illuminate\Support\Facades\Hash; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
| @@ -17,7 +18,7 @@ class Destination extends Component | |||||||
| { | { | ||||||
|     public $resource; |     public $resource; | ||||||
| 
 | 
 | ||||||
|     public $networks = []; |     public Collection $networks; | ||||||
| 
 | 
 | ||||||
|     public function getListeners() |     public function getListeners() | ||||||
|     { |     { | ||||||
| @@ -30,6 +31,7 @@ class Destination extends Component | |||||||
| 
 | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|  |         $this->networks = collect([]); | ||||||
|         $this->loadData(); |         $this->loadData(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -55,38 +57,46 @@ class Destination extends Component | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function stop(int $server_id) |     public function stop($serverId) | ||||||
|     { |     { | ||||||
|         $server = Server::find($server_id); |         try { | ||||||
|         StopApplicationOneServer::run($this->resource, $server); |             $server = Server::ownedByCurrentTeam()->findOrFail($serverId); | ||||||
|         $this->refreshServers(); |             StopApplicationOneServer::run($this->resource, $server); | ||||||
|  |             $this->refreshServers(); | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             return handleError($e, $this); | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function redeploy(int $network_id, int $server_id) |     public function redeploy(int $network_id, int $server_id) | ||||||
|     { |     { | ||||||
|         if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) { |         try { | ||||||
|             $this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>'); |             if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) { | ||||||
|  |                 $this->dispatch('error', 'Failed to deploy.', 'Before deploying to multiple servers, you must first set a Docker image in the General tab.<br>More information here: <a target="_blank" class="underline" href="https://coolify.io/docs/knowledge-base/server/multiple-servers">documentation</a>'); | ||||||
| 
 | 
 | ||||||
|             return; |                 return; | ||||||
|  |             } | ||||||
|  |             $deployment_uuid = new Cuid2; | ||||||
|  |             $server = Server::ownedByCurrentTeam()->findOrFail($server_id); | ||||||
|  |             $destination = $server->standaloneDockers->where('id', $network_id)->firstOrFail(); | ||||||
|  |             queue_application_deployment( | ||||||
|  |                 deployment_uuid: $deployment_uuid, | ||||||
|  |                 application: $this->resource, | ||||||
|  |                 server: $server, | ||||||
|  |                 destination: $destination, | ||||||
|  |                 only_this_server: true, | ||||||
|  |                 no_questions_asked: true, | ||||||
|  |             ); | ||||||
|  | 
 | ||||||
|  |             return redirect()->route('project.application.deployment.show', [ | ||||||
|  |                 'project_uuid' => data_get($this->resource, 'environment.project.uuid'), | ||||||
|  |                 'application_uuid' => data_get($this->resource, 'uuid'), | ||||||
|  |                 'deployment_uuid' => $deployment_uuid, | ||||||
|  |                 'environment_uuid' => data_get($this->resource, 'environment.uuid'), | ||||||
|  |             ]); | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
|         $deployment_uuid = new Cuid2; |  | ||||||
|         $server = Server::find($server_id); |  | ||||||
|         $destination = StandaloneDocker::find($network_id); |  | ||||||
|         queue_application_deployment( |  | ||||||
|             deployment_uuid: $deployment_uuid, |  | ||||||
|             application: $this->resource, |  | ||||||
|             server: $server, |  | ||||||
|             destination: $destination, |  | ||||||
|             only_this_server: true, |  | ||||||
|             no_questions_asked: true, |  | ||||||
|         ); |  | ||||||
| 
 |  | ||||||
|         return redirect()->route('project.application.deployment.show', [ |  | ||||||
|             'project_uuid' => data_get($this->resource, 'environment.project.uuid'), |  | ||||||
|             'application_uuid' => data_get($this->resource, 'uuid'), |  | ||||||
|             'deployment_uuid' => $deployment_uuid, |  | ||||||
|             'environment_name' => data_get($this->resource, 'environment.name'), |  | ||||||
|         ]); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function promote(int $network_id, int $server_id) |     public function promote(int $network_id, int $server_id) | ||||||
| @@ -119,23 +129,27 @@ class Destination extends Component | |||||||
| 
 | 
 | ||||||
|     public function removeServer(int $network_id, int $server_id, $password) |     public function removeServer(int $network_id, int $server_id, $password) | ||||||
|     { |     { | ||||||
|         if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) { |         try { | ||||||
|             if (! Hash::check($password, Auth::user()->password)) { |             if (! data_get(InstanceSettings::get(), 'disable_two_step_confirmation')) { | ||||||
|                 $this->addError('password', 'The provided password is incorrect.'); |                 if (! Hash::check($password, Auth::user()->password)) { | ||||||
|  |                     $this->addError('password', 'The provided password is incorrect.'); | ||||||
|  | 
 | ||||||
|  |                     return; | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) { | ||||||
|  |                 $this->dispatch('error', 'You cannot remove this destination server.', 'You are trying to remove the main server.'); | ||||||
| 
 | 
 | ||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
|  |             $server = Server::ownedByCurrentTeam()->findOrFail($server_id); | ||||||
|  |             StopApplicationOneServer::run($this->resource, $server); | ||||||
|  |             $this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]); | ||||||
|  |             $this->loadData(); | ||||||
|  |             ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id')); | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             return handleError($e, $this); | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         if ($this->resource->destination->server->id == $server_id && $this->resource->destination->id == $network_id) { |  | ||||||
|             $this->dispatch('error', 'You cannot remove this destination server.', 'You are trying to remove the main server.'); |  | ||||||
| 
 |  | ||||||
|             return; |  | ||||||
|         } |  | ||||||
|         $server = Server::find($server_id); |  | ||||||
|         StopApplicationOneServer::run($this->resource, $server); |  | ||||||
|         $this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]); |  | ||||||
|         $this->loadData(); |  | ||||||
|         ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id')); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,7 +4,6 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable; | |||||||
| 
 | 
 | ||||||
| use App\Models\EnvironmentVariable; | use App\Models\EnvironmentVariable; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
| use Visus\Cuid2\Cuid2; |  | ||||||
| 
 | 
 | ||||||
| class All extends Component | class All extends Component | ||||||
| { | { | ||||||
| @@ -14,38 +13,35 @@ class All extends Component | |||||||
| 
 | 
 | ||||||
|     public bool $showPreview = false; |     public bool $showPreview = false; | ||||||
| 
 | 
 | ||||||
|     public ?string $modalId = null; |  | ||||||
| 
 |  | ||||||
|     public ?string $variables = null; |     public ?string $variables = null; | ||||||
| 
 | 
 | ||||||
|     public ?string $variablesPreview = null; |     public ?string $variablesPreview = null; | ||||||
| 
 | 
 | ||||||
|     public string $view = 'normal'; |     public string $view = 'normal'; | ||||||
| 
 | 
 | ||||||
|  |     public bool $is_env_sorting_enabled = false; | ||||||
|  | 
 | ||||||
|     protected $listeners = [ |     protected $listeners = [ | ||||||
|         'saveKey' => 'submit', |         'saveKey' => 'submit', | ||||||
|         'refreshEnvs', |         'refreshEnvs', | ||||||
|         'environmentVariableDeleted' => 'refreshEnvs', |         'environmentVariableDeleted' => 'refreshEnvs', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected $rules = [ |  | ||||||
|         'resource.settings.is_env_sorting_enabled' => 'required|boolean', |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|  |         $this->is_env_sorting_enabled = data_get($this->resource, 'settings.is_env_sorting_enabled', false); | ||||||
|         $this->resourceClass = get_class($this->resource); |         $this->resourceClass = get_class($this->resource); | ||||||
|         $resourceWithPreviews = [\App\Models\Application::class]; |         $resourceWithPreviews = [\App\Models\Application::class]; | ||||||
|         $simpleDockerfile = ! is_null(data_get($this->resource, 'dockerfile')); |         $simpleDockerfile = filled(data_get($this->resource, 'dockerfile')); | ||||||
|         if (str($this->resourceClass)->contains($resourceWithPreviews) && ! $simpleDockerfile) { |         if (str($this->resourceClass)->contains($resourceWithPreviews) && ! $simpleDockerfile) { | ||||||
|             $this->showPreview = true; |             $this->showPreview = true; | ||||||
|         } |         } | ||||||
|         $this->modalId = new Cuid2; |  | ||||||
|         $this->sortEnvironmentVariables(); |         $this->sortEnvironmentVariables(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function instantSave() |     public function instantSave() | ||||||
|     { |     { | ||||||
|  |         $this->resource->settings->is_env_sorting_enabled = $this->is_env_sorting_enabled; | ||||||
|         $this->resource->settings->save(); |         $this->resource->settings->save(); | ||||||
|         $this->sortEnvironmentVariables(); |         $this->sortEnvironmentVariables(); | ||||||
|         $this->dispatch('success', 'Environment variable settings updated.'); |         $this->dispatch('success', 'Environment variable settings updated.'); | ||||||
| @@ -53,7 +49,7 @@ class All extends Component | |||||||
| 
 | 
 | ||||||
|     public function sortEnvironmentVariables() |     public function sortEnvironmentVariables() | ||||||
|     { |     { | ||||||
|         if (! data_get($this->resource, 'settings.is_env_sorting_enabled')) { |         if ($this->is_env_sorting_enabled === false) { | ||||||
|             if ($this->resource->environment_variables) { |             if ($this->resource->environment_variables) { | ||||||
|                 $this->resource->environment_variables = $this->resource->environment_variables->sortBy('order')->values(); |                 $this->resource->environment_variables = $this->resource->environment_variables->sortBy('order')->values(); | ||||||
|             } |             } | ||||||
| @@ -178,35 +174,12 @@ class All extends Component | |||||||
|         $environment->is_multiline = $data['is_multiline'] ?? false; |         $environment->is_multiline = $data['is_multiline'] ?? false; | ||||||
|         $environment->is_literal = $data['is_literal'] ?? false; |         $environment->is_literal = $data['is_literal'] ?? false; | ||||||
|         $environment->is_preview = $data['is_preview'] ?? false; |         $environment->is_preview = $data['is_preview'] ?? false; | ||||||
| 
 |         $environment->resourceable_id = $this->resource->id; | ||||||
|         $resourceType = $this->resource->type(); |         $environment->resourceable_type = $this->resource->getMorphClass(); | ||||||
|         $resourceIdField = $this->getResourceIdField($resourceType); |  | ||||||
| 
 |  | ||||||
|         if ($resourceIdField) { |  | ||||||
|             $environment->$resourceIdField = $this->resource->id; |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         return $environment; |         return $environment; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function getResourceIdField($resourceType) |  | ||||||
|     { |  | ||||||
|         $resourceTypes = [ |  | ||||||
|             'application' => 'application_id', |  | ||||||
|             'standalone-postgresql' => 'standalone_postgresql_id', |  | ||||||
|             'standalone-redis' => 'standalone_redis_id', |  | ||||||
|             'standalone-mongodb' => 'standalone_mongodb_id', |  | ||||||
|             'standalone-mysql' => 'standalone_mysql_id', |  | ||||||
|             'standalone-mariadb' => 'standalone_mariadb_id', |  | ||||||
|             'standalone-keydb' => 'standalone_keydb_id', |  | ||||||
|             'standalone-dragonfly' => 'standalone_dragonfly_id', |  | ||||||
|             'standalone-clickhouse' => 'standalone_clickhouse_id', |  | ||||||
|             'service' => 'service_id', |  | ||||||
|         ]; |  | ||||||
| 
 |  | ||||||
|         return $resourceTypes[$resourceType] ?? null; |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     private function deleteRemovedVariables($isPreview, $variables) |     private function deleteRemovedVariables($isPreview, $variables) | ||||||
|     { |     { | ||||||
|         $method = $isPreview ? 'environment_variables_preview' : 'environment_variables'; |         $method = $isPreview ? 'environment_variables_preview' : 'environment_variables'; | ||||||
| @@ -231,34 +204,14 @@ class All extends Component | |||||||
|                 $environment->is_build_time = false; |                 $environment->is_build_time = false; | ||||||
|                 $environment->is_multiline = false; |                 $environment->is_multiline = false; | ||||||
|                 $environment->is_preview = $isPreview; |                 $environment->is_preview = $isPreview; | ||||||
|  |                 $environment->resourceable_id = $this->resource->id; | ||||||
|  |                 $environment->resourceable_type = $this->resource->getMorphClass(); | ||||||
| 
 | 
 | ||||||
|                 $this->setEnvironmentResourceId($environment); |  | ||||||
|                 $environment->save(); |                 $environment->save(); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     private function setEnvironmentResourceId($environment) |  | ||||||
|     { |  | ||||||
|         $resourceTypes = [ |  | ||||||
|             'application' => 'application_id', |  | ||||||
|             'standalone-postgresql' => 'standalone_postgresql_id', |  | ||||||
|             'standalone-redis' => 'standalone_redis_id', |  | ||||||
|             'standalone-mongodb' => 'standalone_mongodb_id', |  | ||||||
|             'standalone-mysql' => 'standalone_mysql_id', |  | ||||||
|             'standalone-mariadb' => 'standalone_mariadb_id', |  | ||||||
|             'standalone-keydb' => 'standalone_keydb_id', |  | ||||||
|             'standalone-dragonfly' => 'standalone_dragonfly_id', |  | ||||||
|             'standalone-clickhouse' => 'standalone_clickhouse_id', |  | ||||||
|             'service' => 'service_id', |  | ||||||
|         ]; |  | ||||||
| 
 |  | ||||||
|         $resourceType = $this->resource->type(); |  | ||||||
|         if (isset($resourceTypes[$resourceType])) { |  | ||||||
|             $environment->{$resourceTypes[$resourceType]} = $this->resource->id; |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function refreshEnvs() |     public function refreshEnvs() | ||||||
|     { |     { | ||||||
|         $this->resource->refresh(); |         $this->resource->refresh(); | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable; | |||||||
| use App\Models\EnvironmentVariable as ModelsEnvironmentVariable; | use App\Models\EnvironmentVariable as ModelsEnvironmentVariable; | ||||||
| use App\Models\SharedEnvironmentVariable; | use App\Models\SharedEnvironmentVariable; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
| use Visus\Cuid2\Cuid2; |  | ||||||
| 
 | 
 | ||||||
| class Show extends Component | class Show extends Component | ||||||
| { | { | ||||||
| @@ -13,8 +12,6 @@ class Show extends Component | |||||||
| 
 | 
 | ||||||
|     public ModelsEnvironmentVariable|SharedEnvironmentVariable $env; |     public ModelsEnvironmentVariable|SharedEnvironmentVariable $env; | ||||||
| 
 | 
 | ||||||
|     public ?string $modalId = null; |  | ||||||
| 
 |  | ||||||
|     public bool $isDisabled = false; |     public bool $isDisabled = false; | ||||||
| 
 | 
 | ||||||
|     public bool $isLocked = false; |     public bool $isLocked = false; | ||||||
| @@ -23,6 +20,26 @@ class Show extends Component | |||||||
| 
 | 
 | ||||||
|     public string $type; |     public string $type; | ||||||
| 
 | 
 | ||||||
|  |     public string $key; | ||||||
|  | 
 | ||||||
|  |     public ?string $value = null; | ||||||
|  | 
 | ||||||
|  |     public ?string $real_value = null; | ||||||
|  | 
 | ||||||
|  |     public bool $is_shared = false; | ||||||
|  | 
 | ||||||
|  |     public bool $is_build_time = false; | ||||||
|  | 
 | ||||||
|  |     public bool $is_multiline = false; | ||||||
|  | 
 | ||||||
|  |     public bool $is_literal = false; | ||||||
|  | 
 | ||||||
|  |     public bool $is_shown_once = false; | ||||||
|  | 
 | ||||||
|  |     public bool $is_required = false; | ||||||
|  | 
 | ||||||
|  |     public bool $is_really_required = false; | ||||||
|  | 
 | ||||||
|     protected $listeners = [ |     protected $listeners = [ | ||||||
|         'refreshEnvs' => 'refresh', |         'refreshEnvs' => 'refresh', | ||||||
|         'refresh', |         'refresh', | ||||||
| @@ -30,40 +47,59 @@ class Show extends Component | |||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected $rules = [ |     protected $rules = [ | ||||||
|         'env.key' => 'required|string', |         'key' => 'required|string', | ||||||
|         'env.value' => 'nullable', |         'value' => 'nullable', | ||||||
|         'env.is_build_time' => 'required|boolean', |         'is_build_time' => 'required|boolean', | ||||||
|         'env.is_multiline' => 'required|boolean', |         'is_multiline' => 'required|boolean', | ||||||
|         'env.is_literal' => 'required|boolean', |         'is_literal' => 'required|boolean', | ||||||
|         'env.is_shown_once' => 'required|boolean', |         'is_shown_once' => 'required|boolean', | ||||||
|         'env.real_value' => 'nullable', |         'real_value' => 'nullable', | ||||||
|         'env.is_required' => 'required|boolean', |         'is_required' => 'required|boolean', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected $validationAttributes = [ |  | ||||||
|         'env.key' => 'Key', |  | ||||||
|         'env.value' => 'Value', |  | ||||||
|         'env.is_build_time' => 'Build Time', |  | ||||||
|         'env.is_multiline' => 'Multiline', |  | ||||||
|         'env.is_literal' => 'Literal', |  | ||||||
|         'env.is_shown_once' => 'Shown Once', |  | ||||||
|         'env.is_required' => 'Required', |  | ||||||
|     ]; |  | ||||||
| 
 |  | ||||||
|     public function refresh() |  | ||||||
|     { |  | ||||||
|         $this->env->refresh(); |  | ||||||
|         $this->checkEnvs(); |  | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|  |         $this->syncData(); | ||||||
|         if ($this->env->getMorphClass() === \App\Models\SharedEnvironmentVariable::class) { |         if ($this->env->getMorphClass() === \App\Models\SharedEnvironmentVariable::class) { | ||||||
|             $this->isSharedVariable = true; |             $this->isSharedVariable = true; | ||||||
|         } |         } | ||||||
|         $this->modalId = new Cuid2; |  | ||||||
|         $this->parameters = get_route_parameters(); |         $this->parameters = get_route_parameters(); | ||||||
|         $this->checkEnvs(); |         $this->checkEnvs(); | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function refresh() | ||||||
|  |     { | ||||||
|  |         $this->syncData(); | ||||||
|  |         $this->checkEnvs(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function syncData(bool $toModel = false) | ||||||
|  |     { | ||||||
|  |         if ($toModel) { | ||||||
|  |             $this->validate(); | ||||||
|  |             $this->env->key = $this->key; | ||||||
|  |             $this->env->value = $this->value; | ||||||
|  |             $this->env->is_build_time = $this->is_build_time; | ||||||
|  |             $this->env->is_multiline = $this->is_multiline; | ||||||
|  |             $this->env->is_literal = $this->is_literal; | ||||||
|  |             $this->env->is_shown_once = $this->is_shown_once; | ||||||
|  |             $this->env->is_required = $this->is_required; | ||||||
|  |             $this->env->is_shared = $this->is_shared; | ||||||
|  |             $this->env->save(); | ||||||
|  |         } else { | ||||||
|  | 
 | ||||||
|  |             $this->key = $this->env->key; | ||||||
|  |             $this->value = $this->env->value; | ||||||
|  |             $this->is_build_time = $this->env->is_build_time ?? false; | ||||||
|  |             $this->is_multiline = $this->env->is_multiline; | ||||||
|  |             $this->is_literal = $this->env->is_literal; | ||||||
|  |             $this->is_shown_once = $this->env->is_shown_once; | ||||||
|  |             $this->is_required = $this->env->is_required ?? false; | ||||||
|  |             $this->is_really_required = $this->env->is_really_required ?? false; | ||||||
|  |             $this->is_shared = $this->env->is_shared ?? false; | ||||||
|  |             $this->real_value = $this->env->real_value; | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function checkEnvs() |     public function checkEnvs() | ||||||
| @@ -107,17 +143,17 @@ class Show extends Component | |||||||
|         try { |         try { | ||||||
|             if ($this->isSharedVariable) { |             if ($this->isSharedVariable) { | ||||||
|                 $this->validate([ |                 $this->validate([ | ||||||
|                     'env.key' => 'required|string', |                     'key' => 'required|string', | ||||||
|                     'env.value' => 'nullable', |                     'value' => 'nullable', | ||||||
|                     'env.is_shown_once' => 'required|boolean', |                     'is_shown_once' => 'required|boolean', | ||||||
|                 ]); |                 ]); | ||||||
|             } else { |             } else { | ||||||
|                 $this->validate(); |                 $this->validate(); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (! $this->isSharedVariable && $this->env->is_required && str($this->env->real_value)->isEmpty()) { |             if (! $this->isSharedVariable && $this->is_required && str($this->value)->isEmpty()) { | ||||||
|                 $oldValue = $this->env->getOriginal('value'); |                 $oldValue = $this->env->getOriginal('value'); | ||||||
|                 $this->env->value = $oldValue; |                 $this->value = $oldValue; | ||||||
|                 $this->dispatch('error', 'Required environment variable cannot be empty.'); |                 $this->dispatch('error', 'Required environment variable cannot be empty.'); | ||||||
| 
 | 
 | ||||||
|                 return; |                 return; | ||||||
| @@ -126,10 +162,10 @@ class Show extends Component | |||||||
|             $this->serialize(); |             $this->serialize(); | ||||||
| 
 | 
 | ||||||
|             if ($this->isSharedVariable) { |             if ($this->isSharedVariable) { | ||||||
|                 unset($this->env->is_required); |                 unset($this->is_required); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             $this->env->save(); |             $this->syncData(true); | ||||||
|             $this->dispatch('success', 'Environment variable updated.'); |             $this->dispatch('success', 'Environment variable updated.'); | ||||||
|             $this->dispatch('envsUpdated'); |             $this->dispatch('envsUpdated'); | ||||||
|         } catch (\Exception $e) { |         } catch (\Exception $e) { | ||||||
|   | |||||||
| @@ -2,6 +2,12 @@ | |||||||
| 
 | 
 | ||||||
| namespace App\Livewire\Project\Shared; | namespace App\Livewire\Project\Shared; | ||||||
| 
 | 
 | ||||||
|  | use App\Actions\Application\StopApplication; | ||||||
|  | use App\Actions\Database\StartDatabase; | ||||||
|  | use App\Actions\Database\StopDatabase; | ||||||
|  | use App\Actions\Service\StartService; | ||||||
|  | use App\Actions\Service\StopService; | ||||||
|  | use App\Jobs\VolumeCloneJob; | ||||||
| use App\Models\Environment; | use App\Models\Environment; | ||||||
| use App\Models\Project; | use App\Models\Project; | ||||||
| use App\Models\StandaloneDocker; | use App\Models\StandaloneDocker; | ||||||
| @@ -15,21 +21,28 @@ class ResourceOperations extends Component | |||||||
| 
 | 
 | ||||||
|     public $projectUuid; |     public $projectUuid; | ||||||
| 
 | 
 | ||||||
|     public $environmentName; |     public $environmentUuid; | ||||||
| 
 | 
 | ||||||
|     public $projects; |     public $projects; | ||||||
| 
 | 
 | ||||||
|     public $servers; |     public $servers; | ||||||
| 
 | 
 | ||||||
|  |     public bool $cloneVolumeData = false; | ||||||
|  | 
 | ||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|         $parameters = get_route_parameters(); |         $parameters = get_route_parameters(); | ||||||
|         $this->projectUuid = data_get($parameters, 'project_uuid'); |         $this->projectUuid = data_get($parameters, 'project_uuid'); | ||||||
|         $this->environmentName = data_get($parameters, 'environment_name'); |         $this->environmentUuid = data_get($parameters, 'environment_uuid'); | ||||||
|         $this->projects = Project::ownedByCurrentTeam()->get(); |         $this->projects = Project::ownedByCurrentTeam()->get(); | ||||||
|         $this->servers = currentTeam()->servers; |         $this->servers = currentTeam()->servers; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function toggleVolumeCloning(bool $value) | ||||||
|  |     { | ||||||
|  |         $this->cloneVolumeData = $value; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function cloneTo($destination_id) |     public function cloneTo($destination_id) | ||||||
|     { |     { | ||||||
|         $new_destination = StandaloneDocker::find($destination_id); |         $new_destination = StandaloneDocker::find($destination_id); | ||||||
| @@ -41,38 +54,151 @@ class ResourceOperations extends Component | |||||||
|         } |         } | ||||||
|         $uuid = (string) new Cuid2; |         $uuid = (string) new Cuid2; | ||||||
|         $server = $new_destination->server; |         $server = $new_destination->server; | ||||||
|  | 
 | ||||||
|         if ($this->resource->getMorphClass() === \App\Models\Application::class) { |         if ($this->resource->getMorphClass() === \App\Models\Application::class) { | ||||||
|             $new_resource = $this->resource->replicate()->fill([ |             $name = 'clone-of-'.str($this->resource->name)->limit(20).'-'.$uuid; | ||||||
|  |             $applicationSettings = $this->resource->settings; | ||||||
|  |             $url = $this->resource->fqdn; | ||||||
|  | 
 | ||||||
|  |             if ($server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) { | ||||||
|  |                 $url = generateFqdn($server, $uuid); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $new_resource = $this->resource->replicate([ | ||||||
|  |                 'id', | ||||||
|  |                 'created_at', | ||||||
|  |                 'updated_at', | ||||||
|  |                 'additional_servers_count', | ||||||
|  |                 'additional_networks_count', | ||||||
|  |             ])->fill([ | ||||||
|                 'uuid' => $uuid, |                 'uuid' => $uuid, | ||||||
|                 'name' => $this->resource->name.'-clone-'.$uuid, |                 'name' => $name, | ||||||
|                 'fqdn' => generateFqdn($server, $uuid), |                 'fqdn' => $url, | ||||||
|                 'status' => 'exited', |                 'status' => 'exited', | ||||||
|                 'destination_id' => $new_destination->id, |                 'destination_id' => $new_destination->id, | ||||||
|             ]); |             ]); | ||||||
|             $new_resource->save(); |             $new_resource->save(); | ||||||
|             if ($new_resource->destination->server->proxyType() !== 'NONE') { | 
 | ||||||
|  |             if ($new_resource->destination->server->proxyType() !== 'NONE' && $applicationSettings->is_container_label_readonly_enabled === true) { | ||||||
|                 $customLabels = str(implode('|coolify|', generateLabelsApplication($new_resource)))->replace('|coolify|', "\n"); |                 $customLabels = str(implode('|coolify|', generateLabelsApplication($new_resource)))->replace('|coolify|', "\n"); | ||||||
|                 $new_resource->custom_labels = base64_encode($customLabels); |                 $new_resource->custom_labels = base64_encode($customLabels); | ||||||
|                 $new_resource->save(); |                 $new_resource->save(); | ||||||
|             } |             } | ||||||
|             $environmentVaribles = $this->resource->environment_variables()->get(); | 
 | ||||||
|             foreach ($environmentVaribles as $environmentVarible) { |             $new_resource->settings()->delete(); | ||||||
|                 $newEnvironmentVariable = $environmentVarible->replicate()->fill([ |             if ($applicationSettings) { | ||||||
|  |                 $newApplicationSettings = $applicationSettings->replicate([ | ||||||
|  |                     'id', | ||||||
|  |                     'created_at', | ||||||
|  |                     'updated_at', | ||||||
|  |                 ])->fill([ | ||||||
|                     'application_id' => $new_resource->id, |                     'application_id' => $new_resource->id, | ||||||
|                 ]); |                 ]); | ||||||
|                 $newEnvironmentVariable->save(); |                 $newApplicationSettings->save(); | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             $tags = $this->resource->tags; | ||||||
|  |             foreach ($tags as $tag) { | ||||||
|  |                 $new_resource->tags()->attach($tag->id); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $scheduledTasks = $this->resource->scheduled_tasks()->get(); | ||||||
|  |             foreach ($scheduledTasks as $task) { | ||||||
|  |                 $newTask = $task->replicate([ | ||||||
|  |                     'id', | ||||||
|  |                     'created_at', | ||||||
|  |                     'updated_at', | ||||||
|  |                 ])->fill([ | ||||||
|  |                     'uuid' => (string) new Cuid2, | ||||||
|  |                     'application_id' => $new_resource->id, | ||||||
|  |                     'team_id' => currentTeam()->id, | ||||||
|  |                 ]); | ||||||
|  |                 $newTask->save(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $applicationPreviews = $this->resource->previews()->get(); | ||||||
|  |             foreach ($applicationPreviews as $preview) { | ||||||
|  |                 $newPreview = $preview->replicate([ | ||||||
|  |                     'id', | ||||||
|  |                     'created_at', | ||||||
|  |                     'updated_at', | ||||||
|  |                 ])->fill([ | ||||||
|  |                     'application_id' => $new_resource->id, | ||||||
|  |                     'status' => 'exited', | ||||||
|  |                 ]); | ||||||
|  |                 $newPreview->save(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             $persistentVolumes = $this->resource->persistentStorages()->get(); |             $persistentVolumes = $this->resource->persistentStorages()->get(); | ||||||
|             foreach ($persistentVolumes as $volume) { |             foreach ($persistentVolumes as $volume) { | ||||||
|                 $newPersistentVolume = $volume->replicate()->fill([ |                 $newName = ''; | ||||||
|                     'name' => $new_resource->uuid.'-'.str($volume->name)->afterLast('-'), |                 if (str_starts_with($volume->name, $this->resource->uuid)) { | ||||||
|  |                     $newName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid); | ||||||
|  |                 } else { | ||||||
|  |                     $newName = $new_resource->uuid.'-'.str($volume->name)->afterLast('-'); | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $newPersistentVolume = $volume->replicate([ | ||||||
|  |                     'id', | ||||||
|  |                     'created_at', | ||||||
|  |                     'updated_at', | ||||||
|  |                 ])->fill([ | ||||||
|  |                     'name' => $newName, | ||||||
|                     'resource_id' => $new_resource->id, |                     'resource_id' => $new_resource->id, | ||||||
|                 ]); |                 ]); | ||||||
|                 $newPersistentVolume->save(); |                 $newPersistentVolume->save(); | ||||||
|  | 
 | ||||||
|  |                 if ($this->cloneVolumeData) { | ||||||
|  |                     try { | ||||||
|  |                         StopApplication::dispatch($this->resource, false, false); | ||||||
|  |                         $sourceVolume = $volume->name; | ||||||
|  |                         $targetVolume = $newPersistentVolume->name; | ||||||
|  |                         $sourceServer = $this->resource->destination->server; | ||||||
|  |                         $targetServer = $new_resource->destination->server; | ||||||
|  | 
 | ||||||
|  |                         VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); | ||||||
|  | 
 | ||||||
|  |                         queue_application_deployment( | ||||||
|  |                             deployment_uuid: (string) new Cuid2, | ||||||
|  |                             application: $this->resource, | ||||||
|  |                             server: $sourceServer, | ||||||
|  |                             destination: $this->resource->destination, | ||||||
|  |                             no_questions_asked: true | ||||||
|  |                         ); | ||||||
|  |                     } catch (\Exception $e) { | ||||||
|  |                         \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|  |             $fileStorages = $this->resource->fileStorages()->get(); | ||||||
|  |             foreach ($fileStorages as $storage) { | ||||||
|  |                 $newStorage = $storage->replicate([ | ||||||
|  |                     'id', | ||||||
|  |                     'created_at', | ||||||
|  |                     'updated_at', | ||||||
|  |                 ])->fill([ | ||||||
|  |                     'resource_id' => $new_resource->id, | ||||||
|  |                 ]); | ||||||
|  |                 $newStorage->save(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $environmentVaribles = $this->resource->environment_variables()->get(); | ||||||
|  |             foreach ($environmentVaribles as $environmentVarible) { | ||||||
|  |                 $newEnvironmentVariable = $environmentVarible->replicate([ | ||||||
|  |                     'id', | ||||||
|  |                     'created_at', | ||||||
|  |                     'updated_at', | ||||||
|  |                 ])->fill([ | ||||||
|  |                     'resourceable_id' => $new_resource->id, | ||||||
|  |                     'resourceable_type' => $new_resource->getMorphClass(), | ||||||
|  |                 ]); | ||||||
|  |                 $newEnvironmentVariable->save(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             $route = route('project.application.configuration', [ |             $route = route('project.application.configuration', [ | ||||||
|                 'project_uuid' => $this->projectUuid, |                 'project_uuid' => $this->projectUuid, | ||||||
|                 'environment_name' => $this->environmentName, |                 'environment_uuid' => $this->environmentUuid, | ||||||
|                 'application_uuid' => $new_resource->uuid, |                 'application_uuid' => $new_resource->uuid, | ||||||
|             ]).'#resource-operations'; |             ]).'#resource-operations'; | ||||||
| 
 | 
 | ||||||
| @@ -88,7 +214,11 @@ class ResourceOperations extends Component | |||||||
|             $this->resource->getMorphClass() === \App\Models\StandaloneClickhouse::class |             $this->resource->getMorphClass() === \App\Models\StandaloneClickhouse::class | ||||||
|         ) { |         ) { | ||||||
|             $uuid = (string) new Cuid2; |             $uuid = (string) new Cuid2; | ||||||
|             $new_resource = $this->resource->replicate()->fill([ |             $new_resource = $this->resource->replicate([ | ||||||
|  |                 'id', | ||||||
|  |                 'created_at', | ||||||
|  |                 'updated_at', | ||||||
|  |             ])->fill([ | ||||||
|                 'uuid' => $uuid, |                 'uuid' => $uuid, | ||||||
|                 'name' => $this->resource->name.'-clone-'.$uuid, |                 'name' => $this->resource->name.'-clone-'.$uuid, | ||||||
|                 'status' => 'exited', |                 'status' => 'exited', | ||||||
| @@ -96,52 +226,255 @@ class ResourceOperations extends Component | |||||||
|                 'destination_id' => $new_destination->id, |                 'destination_id' => $new_destination->id, | ||||||
|             ]); |             ]); | ||||||
|             $new_resource->save(); |             $new_resource->save(); | ||||||
|  | 
 | ||||||
|  |             $tags = $this->resource->tags; | ||||||
|  |             foreach ($tags as $tag) { | ||||||
|  |                 $new_resource->tags()->attach($tag->id); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $new_resource->persistentStorages()->delete(); | ||||||
|  |             $persistentVolumes = $this->resource->persistentStorages()->get(); | ||||||
|  |             foreach ($persistentVolumes as $volume) { | ||||||
|  |                 $originalName = $volume->name; | ||||||
|  |                 $newName = ''; | ||||||
|  | 
 | ||||||
|  |                 if (str_starts_with($originalName, 'postgres-data-')) { | ||||||
|  |                     $newName = 'postgres-data-'.$new_resource->uuid; | ||||||
|  |                 } elseif (str_starts_with($originalName, 'mysql-data-')) { | ||||||
|  |                     $newName = 'mysql-data-'.$new_resource->uuid; | ||||||
|  |                 } elseif (str_starts_with($originalName, 'redis-data-')) { | ||||||
|  |                     $newName = 'redis-data-'.$new_resource->uuid; | ||||||
|  |                 } elseif (str_starts_with($originalName, 'clickhouse-data-')) { | ||||||
|  |                     $newName = 'clickhouse-data-'.$new_resource->uuid; | ||||||
|  |                 } elseif (str_starts_with($originalName, 'mariadb-data-')) { | ||||||
|  |                     $newName = 'mariadb-data-'.$new_resource->uuid; | ||||||
|  |                 } elseif (str_starts_with($originalName, 'mongodb-data-')) { | ||||||
|  |                     $newName = 'mongodb-data-'.$new_resource->uuid; | ||||||
|  |                 } elseif (str_starts_with($originalName, 'keydb-data-')) { | ||||||
|  |                     $newName = 'keydb-data-'.$new_resource->uuid; | ||||||
|  |                 } elseif (str_starts_with($originalName, 'dragonfly-data-')) { | ||||||
|  |                     $newName = 'dragonfly-data-'.$new_resource->uuid; | ||||||
|  |                 } else { | ||||||
|  |                     if (str_starts_with($volume->name, $this->resource->uuid)) { | ||||||
|  |                         $newName = str($volume->name)->replace($this->resource->uuid, $new_resource->uuid); | ||||||
|  |                     } else { | ||||||
|  |                         $newName = $new_resource->uuid.'-'.$volume->name; | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|  |                 $newPersistentVolume = $volume->replicate([ | ||||||
|  |                     'id', | ||||||
|  |                     'created_at', | ||||||
|  |                     'updated_at', | ||||||
|  |                 ])->fill([ | ||||||
|  |                     'name' => $newName, | ||||||
|  |                     'resource_id' => $new_resource->id, | ||||||
|  |                 ]); | ||||||
|  |                 $newPersistentVolume->save(); | ||||||
|  | 
 | ||||||
|  |                 if ($this->cloneVolumeData) { | ||||||
|  |                     try { | ||||||
|  |                         StopDatabase::dispatch($this->resource); | ||||||
|  |                         $sourceVolume = $volume->name; | ||||||
|  |                         $targetVolume = $newPersistentVolume->name; | ||||||
|  |                         $sourceServer = $this->resource->destination->server; | ||||||
|  |                         $targetServer = $new_resource->destination->server; | ||||||
|  | 
 | ||||||
|  |                         VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); | ||||||
|  | 
 | ||||||
|  |                         StartDatabase::dispatch($this->resource); | ||||||
|  |                     } catch (\Exception $e) { | ||||||
|  |                         \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $fileStorages = $this->resource->fileStorages()->get(); | ||||||
|  |             foreach ($fileStorages as $storage) { | ||||||
|  |                 $newStorage = $storage->replicate([ | ||||||
|  |                     'id', | ||||||
|  |                     'created_at', | ||||||
|  |                     'updated_at', | ||||||
|  |                 ])->fill([ | ||||||
|  |                     'resource_id' => $new_resource->id, | ||||||
|  |                 ]); | ||||||
|  |                 $newStorage->save(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $scheduledBackups = $this->resource->scheduledBackups()->get(); | ||||||
|  |             foreach ($scheduledBackups as $backup) { | ||||||
|  |                 $uuid = (string) new Cuid2; | ||||||
|  |                 $newBackup = $backup->replicate([ | ||||||
|  |                     'id', | ||||||
|  |                     'created_at', | ||||||
|  |                     'updated_at', | ||||||
|  |                 ])->fill([ | ||||||
|  |                     'uuid' => $uuid, | ||||||
|  |                     'database_id' => $new_resource->id, | ||||||
|  |                     'database_type' => $new_resource->getMorphClass(), | ||||||
|  |                     'team_id' => currentTeam()->id, | ||||||
|  |                 ]); | ||||||
|  |                 $newBackup->save(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             $environmentVaribles = $this->resource->environment_variables()->get(); |             $environmentVaribles = $this->resource->environment_variables()->get(); | ||||||
|             foreach ($environmentVaribles as $environmentVarible) { |             foreach ($environmentVaribles as $environmentVarible) { | ||||||
|                 $payload = []; |                 $payload = [ | ||||||
|                 if ($this->resource->type() === 'standalone-postgresql') { |                     'resourceable_id' => $new_resource->id, | ||||||
|                     $payload['standalone_postgresql_id'] = $new_resource->id; |                     'resourceable_type' => $new_resource->getMorphClass(), | ||||||
|                 } elseif ($this->resource->type() === 'standalone-redis') { |                 ]; | ||||||
|                     $payload['standalone_redis_id'] = $new_resource->id; |                 $newEnvironmentVariable = $environmentVarible->replicate([ | ||||||
|                 } elseif ($this->resource->type() === 'standalone-mongodb') { |                     'id', | ||||||
|                     $payload['standalone_mongodb_id'] = $new_resource->id; |                     'created_at', | ||||||
|                 } elseif ($this->resource->type() === 'standalone-mysql') { |                     'updated_at', | ||||||
|                     $payload['standalone_mysql_id'] = $new_resource->id; |                 ])->fill($payload); | ||||||
|                 } elseif ($this->resource->type() === 'standalone-mariadb') { |  | ||||||
|                     $payload['standalone_mariadb_id'] = $new_resource->id; |  | ||||||
|                 } |  | ||||||
|                 $newEnvironmentVariable = $environmentVarible->replicate()->fill($payload); |  | ||||||
|                 $newEnvironmentVariable->save(); |                 $newEnvironmentVariable->save(); | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|             $route = route('project.database.configuration', [ |             $route = route('project.database.configuration', [ | ||||||
|                 'project_uuid' => $this->projectUuid, |                 'project_uuid' => $this->projectUuid, | ||||||
|                 'environment_name' => $this->environmentName, |                 'environment_uuid' => $this->environmentUuid, | ||||||
|                 'database_uuid' => $new_resource->uuid, |                 'database_uuid' => $new_resource->uuid, | ||||||
|             ]).'#resource-operations'; |             ]).'#resource-operations'; | ||||||
| 
 | 
 | ||||||
|             return redirect()->to($route); |             return redirect()->to($route); | ||||||
|         } elseif ($this->resource->type() === 'service') { |         } elseif ($this->resource->type() === 'service') { | ||||||
|             $uuid = (string) new Cuid2; |             $uuid = (string) new Cuid2; | ||||||
|             $new_resource = $this->resource->replicate()->fill([ |             $new_resource = $this->resource->replicate([ | ||||||
|  |                 'id', | ||||||
|  |                 'created_at', | ||||||
|  |                 'updated_at', | ||||||
|  |             ])->fill([ | ||||||
|                 'uuid' => $uuid, |                 'uuid' => $uuid, | ||||||
|                 'name' => $this->resource->name.'-clone-'.$uuid, |                 'name' => $this->resource->name.'-clone-'.$uuid, | ||||||
|                 'destination_id' => $new_destination->id, |                 'destination_id' => $new_destination->id, | ||||||
|  |                 'destination_type' => $new_destination->getMorphClass(), | ||||||
|  |                 'server_id' => $new_destination->server_id, // server_id is probably not needed anymore because of the new polymorphic relationships (here it is needed for clone to a different server to work - but maybe we can drop the column)
 | ||||||
|             ]); |             ]); | ||||||
|  | 
 | ||||||
|             $new_resource->save(); |             $new_resource->save(); | ||||||
|  | 
 | ||||||
|  |             $tags = $this->resource->tags; | ||||||
|  |             foreach ($tags as $tag) { | ||||||
|  |                 $new_resource->tags()->attach($tag->id); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $scheduledTasks = $this->resource->scheduled_tasks()->get(); | ||||||
|  |             foreach ($scheduledTasks as $task) { | ||||||
|  |                 $newTask = $task->replicate([ | ||||||
|  |                     'id', | ||||||
|  |                     'created_at', | ||||||
|  |                     'updated_at', | ||||||
|  |                 ])->fill([ | ||||||
|  |                     'uuid' => (string) new Cuid2, | ||||||
|  |                     'service_id' => $new_resource->id, | ||||||
|  |                     'team_id' => currentTeam()->id, | ||||||
|  |                 ]); | ||||||
|  |                 $newTask->save(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             $environmentVariables = $this->resource->environment_variables()->get(); | ||||||
|  |             foreach ($environmentVariables as $environmentVariable) { | ||||||
|  |                 $newEnvironmentVariable = $environmentVariable->replicate([ | ||||||
|  |                     'id', | ||||||
|  |                     'created_at', | ||||||
|  |                     'updated_at', | ||||||
|  |                 ])->fill([ | ||||||
|  |                     'resourceable_id' => $new_resource->id, | ||||||
|  |                     'resourceable_type' => $new_resource->getMorphClass(), | ||||||
|  |                 ]); | ||||||
|  |                 $newEnvironmentVariable->save(); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|             foreach ($new_resource->applications() as $application) { |             foreach ($new_resource->applications() as $application) { | ||||||
|                 $application->update([ |                 $application->update([ | ||||||
|                     'status' => 'exited', |                     'status' => 'exited', | ||||||
|                 ]); |                 ]); | ||||||
|  | 
 | ||||||
|  |                 $persistentVolumes = $application->persistentStorages()->get(); | ||||||
|  |                 foreach ($persistentVolumes as $volume) { | ||||||
|  |                     $newName = ''; | ||||||
|  |                     if (str_starts_with($volume->name, $volume->resource->uuid)) { | ||||||
|  |                         $newName = str($volume->name)->replace($volume->resource->uuid, $application->uuid); | ||||||
|  |                     } else { | ||||||
|  |                         $newName = $application->uuid.'-'.str($volume->name)->afterLast('-'); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     $newPersistentVolume = $volume->replicate([ | ||||||
|  |                         'id', | ||||||
|  |                         'created_at', | ||||||
|  |                         'updated_at', | ||||||
|  |                     ])->fill([ | ||||||
|  |                         'name' => $newName, | ||||||
|  |                         'resource_id' => $application->id, | ||||||
|  |                     ]); | ||||||
|  |                     $newPersistentVolume->save(); | ||||||
|  | 
 | ||||||
|  |                     if ($this->cloneVolumeData) { | ||||||
|  |                         try { | ||||||
|  |                             StopService::dispatch($application, false, false); | ||||||
|  |                             $sourceVolume = $volume->name; | ||||||
|  |                             $targetVolume = $newPersistentVolume->name; | ||||||
|  |                             $sourceServer = $application->service->destination->server; | ||||||
|  |                             $targetServer = $new_resource->destination->server; | ||||||
|  | 
 | ||||||
|  |                             VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); | ||||||
|  | 
 | ||||||
|  |                             StartService::dispatch($application); | ||||||
|  |                         } catch (\Exception $e) { | ||||||
|  |                             \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|             foreach ($new_resource->databases() as $database) { |             foreach ($new_resource->databases() as $database) { | ||||||
|                 $database->update([ |                 $database->update([ | ||||||
|                     'status' => 'exited', |                     'status' => 'exited', | ||||||
|                 ]); |                 ]); | ||||||
|  | 
 | ||||||
|  |                 $persistentVolumes = $database->persistentStorages()->get(); | ||||||
|  |                 foreach ($persistentVolumes as $volume) { | ||||||
|  |                     $newName = ''; | ||||||
|  |                     if (str_starts_with($volume->name, $volume->resource->uuid)) { | ||||||
|  |                         $newName = str($volume->name)->replace($volume->resource->uuid, $database->uuid); | ||||||
|  |                     } else { | ||||||
|  |                         $newName = $database->uuid.'-'.str($volume->name)->afterLast('-'); | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     $newPersistentVolume = $volume->replicate([ | ||||||
|  |                         'id', | ||||||
|  |                         'created_at', | ||||||
|  |                         'updated_at', | ||||||
|  |                     ])->fill([ | ||||||
|  |                         'name' => $newName, | ||||||
|  |                         'resource_id' => $database->id, | ||||||
|  |                     ]); | ||||||
|  |                     $newPersistentVolume->save(); | ||||||
|  | 
 | ||||||
|  |                     if ($this->cloneVolumeData) { | ||||||
|  |                         try { | ||||||
|  |                             StopService::dispatch($database->service, false, false); | ||||||
|  |                             $sourceVolume = $volume->name; | ||||||
|  |                             $targetVolume = $newPersistentVolume->name; | ||||||
|  |                             $sourceServer = $database->service->destination->server; | ||||||
|  |                             $targetServer = $new_resource->destination->server; | ||||||
|  | 
 | ||||||
|  |                             VolumeCloneJob::dispatch($sourceVolume, $targetVolume, $sourceServer, $targetServer, $newPersistentVolume); | ||||||
|  | 
 | ||||||
|  |                             StartService::dispatch($database->service); | ||||||
|  |                         } catch (\Exception $e) { | ||||||
|  |                             \Log::error('Failed to copy volume data for '.$volume->name.': '.$e->getMessage()); | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|             $new_resource->parse(); |             $new_resource->parse(); | ||||||
|  | 
 | ||||||
|             $route = route('project.service.configuration', [ |             $route = route('project.service.configuration', [ | ||||||
|                 'project_uuid' => $this->projectUuid, |                 'project_uuid' => $this->projectUuid, | ||||||
|                 'environment_name' => $this->environmentName, |                 'environment_uuid' => $this->environmentUuid, | ||||||
|                 'service_uuid' => $new_resource->uuid, |                 'service_uuid' => $new_resource->uuid, | ||||||
|             ]).'#resource-operations'; |             ]).'#resource-operations'; | ||||||
| 
 | 
 | ||||||
| @@ -159,7 +492,7 @@ class ResourceOperations extends Component | |||||||
|             if ($this->resource->type() === 'application') { |             if ($this->resource->type() === 'application') { | ||||||
|                 $route = route('project.application.configuration', [ |                 $route = route('project.application.configuration', [ | ||||||
|                     'project_uuid' => $new_environment->project->uuid, |                     'project_uuid' => $new_environment->project->uuid, | ||||||
|                     'environment_name' => $new_environment->name, |                     'environment_uuid' => $new_environment->uuid, | ||||||
|                     'application_uuid' => $this->resource->uuid, |                     'application_uuid' => $this->resource->uuid, | ||||||
|                 ]).'#resource-operations'; |                 ]).'#resource-operations'; | ||||||
| 
 | 
 | ||||||
| @@ -167,7 +500,7 @@ class ResourceOperations extends Component | |||||||
|             } elseif (str($this->resource->type())->startsWith('standalone-')) { |             } elseif (str($this->resource->type())->startsWith('standalone-')) { | ||||||
|                 $route = route('project.database.configuration', [ |                 $route = route('project.database.configuration', [ | ||||||
|                     'project_uuid' => $new_environment->project->uuid, |                     'project_uuid' => $new_environment->project->uuid, | ||||||
|                     'environment_name' => $new_environment->name, |                     'environment_uuid' => $new_environment->uuid, | ||||||
|                     'database_uuid' => $this->resource->uuid, |                     'database_uuid' => $this->resource->uuid, | ||||||
|                 ]).'#resource-operations'; |                 ]).'#resource-operations'; | ||||||
| 
 | 
 | ||||||
| @@ -175,7 +508,7 @@ class ResourceOperations extends Component | |||||||
|             } elseif ($this->resource->type() === 'service') { |             } elseif ($this->resource->type() === 'service') { | ||||||
|                 $route = route('project.service.configuration', [ |                 $route = route('project.service.configuration', [ | ||||||
|                     'project_uuid' => $new_environment->project->uuid, |                     'project_uuid' => $new_environment->project->uuid, | ||||||
|                     'environment_name' => $new_environment->name, |                     'environment_uuid' => $new_environment->uuid, | ||||||
|                     'service_uuid' => $this->resource->uuid, |                     'service_uuid' => $this->resource->uuid, | ||||||
|                 ]).'#resource-operations'; |                 ]).'#resource-operations'; | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -46,7 +46,7 @@ class Show extends Component | |||||||
|     #[Locked]
 |     #[Locked]
 | ||||||
|     public string $task_uuid; |     public string $task_uuid; | ||||||
| 
 | 
 | ||||||
|     public function mount(string $task_uuid, string $project_uuid, string $environment_name, ?string $application_uuid = null, ?string $service_uuid = null) |     public function mount(string $task_uuid, string $project_uuid, string $environment_uuid, ?string $application_uuid = null, ?string $service_uuid = null) | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
|             $this->task_uuid = $task_uuid; |             $this->task_uuid = $task_uuid; | ||||||
| @@ -60,7 +60,7 @@ class Show extends Component | |||||||
|                 $this->resource = Service::ownedByCurrentTeam()->where('uuid', $service_uuid)->firstOrFail(); |                 $this->resource = Service::ownedByCurrentTeam()->where('uuid', $service_uuid)->firstOrFail(); | ||||||
|             } |             } | ||||||
|             $this->parameters = [ |             $this->parameters = [ | ||||||
|                 'environment_name' => $environment_name, |                 'environment_uuid' => $environment_uuid, | ||||||
|                 'project_uuid' => $project_uuid, |                 'project_uuid' => $project_uuid, | ||||||
|                 'application_uuid' => $application_uuid, |                 'application_uuid' => $application_uuid, | ||||||
|                 'service_uuid' => $service_uuid, |                 'service_uuid' => $service_uuid, | ||||||
|   | |||||||
| @@ -81,11 +81,18 @@ class Add extends Component | |||||||
|                 'file_storage_path' => 'string', |                 'file_storage_path' => 'string', | ||||||
|                 'file_storage_content' => 'nullable|string', |                 'file_storage_content' => 'nullable|string', | ||||||
|             ]); |             ]); | ||||||
|  | 
 | ||||||
|             $this->file_storage_path = trim($this->file_storage_path); |             $this->file_storage_path = trim($this->file_storage_path); | ||||||
|             $this->file_storage_path = str($this->file_storage_path)->start('/')->value(); |             $this->file_storage_path = str($this->file_storage_path)->start('/')->value(); | ||||||
|  | 
 | ||||||
|             if ($this->resource->getMorphClass() === \App\Models\Application::class) { |             if ($this->resource->getMorphClass() === \App\Models\Application::class) { | ||||||
|                 $fs_path = application_configuration_dir().'/'.$this->resource->uuid.$this->file_storage_path; |                 $fs_path = application_configuration_dir().'/'.$this->resource->uuid.$this->file_storage_path; | ||||||
|  |             } elseif (str($this->resource->getMorphClass())->contains('Standalone')) { | ||||||
|  |                 $fs_path = database_configuration_dir().'/'.$this->resource->uuid.$this->file_storage_path; | ||||||
|  |             } else { | ||||||
|  |                 throw new \Exception('No valid resource type for file mount storage type!'); | ||||||
|             } |             } | ||||||
|  | 
 | ||||||
|             LocalFileVolume::create( |             LocalFileVolume::create( | ||||||
|                 [ |                 [ | ||||||
|                     'fs_path' => $fs_path, |                     'fs_path' => $fs_path, | ||||||
| @@ -109,10 +116,12 @@ class Add extends Component | |||||||
|                 'file_storage_directory_source' => 'string', |                 'file_storage_directory_source' => 'string', | ||||||
|                 'file_storage_directory_destination' => 'string', |                 'file_storage_directory_destination' => 'string', | ||||||
|             ]); |             ]); | ||||||
|  | 
 | ||||||
|             $this->file_storage_directory_source = trim($this->file_storage_directory_source); |             $this->file_storage_directory_source = trim($this->file_storage_directory_source); | ||||||
|             $this->file_storage_directory_source = str($this->file_storage_directory_source)->start('/')->value(); |             $this->file_storage_directory_source = str($this->file_storage_directory_source)->start('/')->value(); | ||||||
|             $this->file_storage_directory_destination = trim($this->file_storage_directory_destination); |             $this->file_storage_directory_destination = trim($this->file_storage_directory_destination); | ||||||
|             $this->file_storage_directory_destination = str($this->file_storage_directory_destination)->start('/')->value(); |             $this->file_storage_directory_destination = str($this->file_storage_directory_destination)->start('/')->value(); | ||||||
|  | 
 | ||||||
|             LocalFileVolume::create( |             LocalFileVolume::create( | ||||||
|                 [ |                 [ | ||||||
|                     'fs_path' => $this->file_storage_directory_source, |                     'fs_path' => $this->file_storage_directory_source, | ||||||
|   | |||||||
| @@ -6,6 +6,7 @@ use App\Models\Environment; | |||||||
| use App\Models\Project; | use App\Models\Project; | ||||||
| use Livewire\Attributes\Validate; | use Livewire\Attributes\Validate; | ||||||
| use Livewire\Component; | use Livewire\Component; | ||||||
|  | use Visus\Cuid2\Cuid2; | ||||||
| 
 | 
 | ||||||
| class Show extends Component | class Show extends Component | ||||||
| { | { | ||||||
| @@ -33,17 +34,26 @@ class Show extends Component | |||||||
|             $environment = Environment::create([ |             $environment = Environment::create([ | ||||||
|                 'name' => $this->name, |                 'name' => $this->name, | ||||||
|                 'project_id' => $this->project->id, |                 'project_id' => $this->project->id, | ||||||
|  |                 'uuid' => (string) new Cuid2, | ||||||
|             ]); |             ]); | ||||||
| 
 | 
 | ||||||
|             return redirect()->route('project.resource.index', [ |             return redirect()->route('project.resource.index', [ | ||||||
|                 'project_uuid' => $this->project->uuid, |                 'project_uuid' => $this->project->uuid, | ||||||
|                 'environment_name' => $environment->name, |                 'environment_uuid' => $environment->uuid, | ||||||
|             ]); |             ]); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|             handleError($e, $this); |             handleError($e, $this); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function navigateToEnvironment($projectUuid, $environmentUuid) | ||||||
|  |     { | ||||||
|  |         return redirect()->route('project.resource.index', [ | ||||||
|  |             'project_uuid' => $projectUuid, | ||||||
|  |             'environment_uuid' => $environmentUuid, | ||||||
|  |         ]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function render() |     public function render() | ||||||
|     { |     { | ||||||
|         return view('livewire.project.show'); |         return view('livewire.project.show'); | ||||||
|   | |||||||
| @@ -13,14 +13,11 @@ class Advanced extends Component | |||||||
| 
 | 
 | ||||||
|     public array $parameters = []; |     public array $parameters = []; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['integer', 'min:1'])]
 |     #[Validate(['string'])]
 | ||||||
|     public int $concurrentBuilds = 1; |     public string $serverDiskUsageCheckFrequency = '0 23 * * *'; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['integer', 'min:1'])]
 |     #[Validate(['integer', 'min:1', 'max:99'])]
 | ||||||
|     public int $dynamicTimeout = 1; |     public int $serverDiskUsageNotificationThreshold = 50; | ||||||
| 
 |  | ||||||
|     #[Validate('boolean')]
 |  | ||||||
|     public bool $forceDockerCleanup = false; |  | ||||||
| 
 | 
 | ||||||
|     #[Validate(['string', 'required'])]
 |     #[Validate(['string', 'required'])]
 | ||||||
|     public string $dockerCleanupFrequency = '*/10 * * * *'; |     public string $dockerCleanupFrequency = '*/10 * * * *'; | ||||||
| @@ -28,8 +25,8 @@ class Advanced extends Component | |||||||
|     #[Validate(['integer', 'min:1', 'max:99'])]
 |     #[Validate(['integer', 'min:1', 'max:99'])]
 | ||||||
|     public int $dockerCleanupThreshold = 10; |     public int $dockerCleanupThreshold = 10; | ||||||
| 
 | 
 | ||||||
|     #[Validate(['integer', 'min:1', 'max:99'])]
 |     #[Validate('boolean')]
 | ||||||
|     public int $serverDiskUsageNotificationThreshold = 50; |     public bool $forceDockerCleanup = false; | ||||||
| 
 | 
 | ||||||
|     #[Validate('boolean')]
 |     #[Validate('boolean')]
 | ||||||
|     public bool $deleteUnusedVolumes = false; |     public bool $deleteUnusedVolumes = false; | ||||||
| @@ -37,6 +34,12 @@ class Advanced extends Component | |||||||
|     #[Validate('boolean')]
 |     #[Validate('boolean')]
 | ||||||
|     public bool $deleteUnusedNetworks = false; |     public bool $deleteUnusedNetworks = false; | ||||||
| 
 | 
 | ||||||
|  |     #[Validate(['integer', 'min:1'])]
 | ||||||
|  |     public int $concurrentBuilds = 1; | ||||||
|  | 
 | ||||||
|  |     #[Validate(['integer', 'min:1'])]
 | ||||||
|  |     public int $dynamicTimeout = 1; | ||||||
|  | 
 | ||||||
|     public function mount(string $server_uuid) |     public function mount(string $server_uuid) | ||||||
|     { |     { | ||||||
|         try { |         try { | ||||||
| @@ -60,6 +63,7 @@ class Advanced extends Component | |||||||
|             $this->server->settings->server_disk_usage_notification_threshold = $this->serverDiskUsageNotificationThreshold; |             $this->server->settings->server_disk_usage_notification_threshold = $this->serverDiskUsageNotificationThreshold; | ||||||
|             $this->server->settings->delete_unused_volumes = $this->deleteUnusedVolumes; |             $this->server->settings->delete_unused_volumes = $this->deleteUnusedVolumes; | ||||||
|             $this->server->settings->delete_unused_networks = $this->deleteUnusedNetworks; |             $this->server->settings->delete_unused_networks = $this->deleteUnusedNetworks; | ||||||
|  |             $this->server->settings->server_disk_usage_check_frequency = $this->serverDiskUsageCheckFrequency; | ||||||
|             $this->server->settings->save(); |             $this->server->settings->save(); | ||||||
|         } else { |         } else { | ||||||
|             $this->concurrentBuilds = $this->server->settings->concurrent_builds; |             $this->concurrentBuilds = $this->server->settings->concurrent_builds; | ||||||
| @@ -70,6 +74,7 @@ class Advanced extends Component | |||||||
|             $this->serverDiskUsageNotificationThreshold = $this->server->settings->server_disk_usage_notification_threshold; |             $this->serverDiskUsageNotificationThreshold = $this->server->settings->server_disk_usage_notification_threshold; | ||||||
|             $this->deleteUnusedVolumes = $this->server->settings->delete_unused_volumes; |             $this->deleteUnusedVolumes = $this->server->settings->delete_unused_volumes; | ||||||
|             $this->deleteUnusedNetworks = $this->server->settings->delete_unused_networks; |             $this->deleteUnusedNetworks = $this->server->settings->delete_unused_networks; | ||||||
|  |             $this->serverDiskUsageCheckFrequency = $this->server->settings->server_disk_usage_check_frequency; | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -100,6 +105,10 @@ class Advanced extends Component | |||||||
|                 $this->dockerCleanupFrequency = $this->server->settings->getOriginal('docker_cleanup_frequency'); |                 $this->dockerCleanupFrequency = $this->server->settings->getOriginal('docker_cleanup_frequency'); | ||||||
|                 throw new \Exception('Invalid Cron / Human expression for Docker Cleanup Frequency.'); |                 throw new \Exception('Invalid Cron / Human expression for Docker Cleanup Frequency.'); | ||||||
|             } |             } | ||||||
|  |             if (! validate_cron_expression($this->serverDiskUsageCheckFrequency)) { | ||||||
|  |                 $this->serverDiskUsageCheckFrequency = $this->server->settings->getOriginal('server_disk_usage_check_frequency'); | ||||||
|  |                 throw new \Exception('Invalid Cron / Human expression for Disk Usage Check Frequency.'); | ||||||
|  |             } | ||||||
|             $this->syncData(true); |             $this->syncData(true); | ||||||
|             $this->dispatch('success', 'Server updated.'); |             $this->dispatch('success', 'Server updated.'); | ||||||
|         } catch (\Throwable $e) { |         } catch (\Throwable $e) { | ||||||
|   | |||||||
| @@ -226,16 +226,18 @@ class Index extends Component | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function toggleTwoStepConfirmation($password) |     public function toggleTwoStepConfirmation($password): bool | ||||||
|     { |     { | ||||||
|         if (! Hash::check($password, Auth::user()->password)) { |         if (! Hash::check($password, Auth::user()->password)) { | ||||||
|             $this->addError('password', 'The provided password is incorrect.'); |             $this->addError('password', 'The provided password is incorrect.'); | ||||||
| 
 | 
 | ||||||
|             return; |             return false; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         $this->settings->disable_two_step_confirmation = $this->disable_two_step_confirmation = true; |         $this->settings->disable_two_step_confirmation = $this->disable_two_step_confirmation = true; | ||||||
|         $this->settings->save(); |         $this->settings->save(); | ||||||
|         $this->dispatch('success', 'Two step confirmation has been disabled.'); |         $this->dispatch('success', 'Two step confirmation has been disabled.'); | ||||||
|  | 
 | ||||||
|  |         return true; | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -42,8 +42,8 @@ class Show extends Component | |||||||
|     public function mount() |     public function mount() | ||||||
|     { |     { | ||||||
|         $this->parameters = get_route_parameters(); |         $this->parameters = get_route_parameters(); | ||||||
|         $this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->first(); |         $this->project = Project::ownedByCurrentTeam()->where('uuid', request()->route('project_uuid'))->firstOrFail(); | ||||||
|         $this->environment = $this->project->environments()->where('name', request()->route('environment_name'))->first(); |         $this->environment = $this->project->environments()->where('uuid', request()->route('environment_uuid'))->firstOrFail(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function render() |     public function render() | ||||||
|   | |||||||
| @@ -130,14 +130,14 @@ class Change extends Component | |||||||
|                 } else { |                 } else { | ||||||
|                     $parameters = data_get(session('from'), 'parameters'); |                     $parameters = data_get(session('from'), 'parameters'); | ||||||
|                     $back = data_get(session('from'), 'back'); |                     $back = data_get(session('from'), 'back'); | ||||||
|                     $environment_name = data_get($parameters, 'environment_name'); |                     $environment_uuid = data_get($parameters, 'environment_uuid'); | ||||||
|                     $project_uuid = data_get($parameters, 'project_uuid'); |                     $project_uuid = data_get($parameters, 'project_uuid'); | ||||||
|                     $type = data_get($parameters, 'type'); |                     $type = data_get($parameters, 'type'); | ||||||
|                     $destination = data_get($parameters, 'destination'); |                     $destination = data_get($parameters, 'destination'); | ||||||
|                     session()->forget('from'); |                     session()->forget('from'); | ||||||
| 
 | 
 | ||||||
|                     return redirect()->route($back, [ |                     return redirect()->route($back, [ | ||||||
|                         'environment_name' => $environment_name, |                         'environment_uuid' => $environment_uuid, | ||||||
|                         'project_uuid' => $project_uuid, |                         'project_uuid' => $project_uuid, | ||||||
|                         'type' => $type, |                         'type' => $type, | ||||||
|                         'destination' => $destination, |                         'destination' => $destination, | ||||||
|   | |||||||
| @@ -325,7 +325,7 @@ class Application extends BaseModel | |||||||
|         if (data_get($this, 'environment.project.uuid')) { |         if (data_get($this, 'environment.project.uuid')) { | ||||||
|             return route('project.application.configuration', [ |             return route('project.application.configuration', [ | ||||||
|                 'project_uuid' => data_get($this, 'environment.project.uuid'), |                 'project_uuid' => data_get($this, 'environment.project.uuid'), | ||||||
|                 'environment_name' => data_get($this, 'environment.name'), |                 'environment_uuid' => data_get($this, 'environment.uuid'), | ||||||
|                 'application_uuid' => data_get($this, 'uuid'), |                 'application_uuid' => data_get($this, 'uuid'), | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
| @@ -338,7 +338,7 @@ class Application extends BaseModel | |||||||
|         if (data_get($this, 'environment.project.uuid')) { |         if (data_get($this, 'environment.project.uuid')) { | ||||||
|             $route = route('project.application.scheduled-tasks', [ |             $route = route('project.application.scheduled-tasks', [ | ||||||
|                 'project_uuid' => data_get($this, 'environment.project.uuid'), |                 'project_uuid' => data_get($this, 'environment.project.uuid'), | ||||||
|                 'environment_name' => data_get($this, 'environment.name'), |                 'environment_uuid' => data_get($this, 'environment.uuid'), | ||||||
|                 'application_uuid' => data_get($this, 'uuid'), |                 'application_uuid' => data_get($this, 'uuid'), | ||||||
|                 'task_uuid' => $task_uuid, |                 'task_uuid' => $task_uuid, | ||||||
|             ]); |             ]); | ||||||
| @@ -610,7 +610,7 @@ class Application extends BaseModel | |||||||
|             }, |             }, | ||||||
|             get: function ($value) { |             get: function ($value) { | ||||||
|                 if ($this->additional_servers->count() === 0) { |                 if ($this->additional_servers->count() === 0) { | ||||||
|                     //running (healthy)
 |                     // running (healthy)
 | ||||||
|                     if (str($value)->contains('(')) { |                     if (str($value)->contains('(')) { | ||||||
|                         $status = str($value)->before('(')->trim()->value(); |                         $status = str($value)->before('(')->trim()->value(); | ||||||
|                         $health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy'; |                         $health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy'; | ||||||
| @@ -695,46 +695,62 @@ class Application extends BaseModel | |||||||
|         return $this->settings->is_static ? [80] : $this->ports_exposes_array; |         return $this->settings->is_static ? [80] : $this->ports_exposes_array; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function environment_variables(): HasMany |     public function environment_variables() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->orderBy('key', 'asc'); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->where('is_preview', false) | ||||||
|  |             ->orderBy('key', 'asc'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function runtime_environment_variables(): HasMany |     public function runtime_environment_variables() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->where('key', 'not like', 'NIXPACKS_%'); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->where('is_preview', false) | ||||||
|  |             ->where('key', 'not like', 'NIXPACKS_%'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     // Preview Deployments
 |     public function build_environment_variables() | ||||||
| 
 |  | ||||||
|     public function build_environment_variables(): HasMany |  | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->where('is_build_time', true)->where('key', 'not like', 'NIXPACKS_%'); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->where('is_preview', false) | ||||||
|  |             ->where('is_build_time', true) | ||||||
|  |             ->where('key', 'not like', 'NIXPACKS_%'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function nixpacks_environment_variables(): HasMany |     public function nixpacks_environment_variables() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class)->where('is_preview', false)->where('key', 'like', 'NIXPACKS_%'); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->where('is_preview', false) | ||||||
|  |             ->where('key', 'like', 'NIXPACKS_%'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function environment_variables_preview(): HasMany |     public function environment_variables_preview() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderBy('key', 'asc'); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->where('is_preview', true) | ||||||
|  |             ->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function runtime_environment_variables_preview(): HasMany |     public function runtime_environment_variables_preview() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->where('key', 'not like', 'NIXPACKS_%'); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->where('is_preview', true) | ||||||
|  |             ->where('key', 'not like', 'NIXPACKS_%'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function build_environment_variables_preview(): HasMany |     public function build_environment_variables_preview() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->where('is_build_time', true)->where('key', 'not like', 'NIXPACKS_%'); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->where('is_preview', true) | ||||||
|  |             ->where('is_build_time', true) | ||||||
|  |             ->where('key', 'not like', 'NIXPACKS_%'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function nixpacks_environment_variables_preview(): HasMany |     public function nixpacks_environment_variables_preview() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->where('key', 'like', 'NIXPACKS_%'); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->where('is_preview', true) | ||||||
|  |             ->where('key', 'like', 'NIXPACKS_%'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function scheduled_tasks(): HasMany |     public function scheduled_tasks(): HasMany | ||||||
|   | |||||||
| @@ -70,6 +70,11 @@ class ApplicationDeploymentQueue extends Model | |||||||
|         return collect(json_decode($this->logs))->where('name', $name)->first()?->output ?? null; |         return collect(json_decode($this->logs))->where('name', $name)->first()?->output ?? null; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     public function getHorizonJobStatus() | ||||||
|  |     { | ||||||
|  |         return getJobStatus($this->horizon_job_id); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function commitMessage() |     public function commitMessage() | ||||||
|     { |     { | ||||||
|         if (empty($this->commit_message) || is_null($this->commit_message)) { |         if (empty($this->commit_message) || is_null($this->commit_message)) { | ||||||
|   | |||||||
| @@ -3,7 +3,6 @@ | |||||||
| namespace App\Models; | namespace App\Models; | ||||||
| 
 | 
 | ||||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | use Illuminate\Database\Eloquent\Casts\Attribute; | ||||||
| use Illuminate\Database\Eloquent\Model; |  | ||||||
| use OpenApi\Attributes as OA; | use OpenApi\Attributes as OA; | ||||||
| 
 | 
 | ||||||
| #[OA\Schema(
 | #[OA\Schema(
 | ||||||
| @@ -18,7 +17,7 @@ use OpenApi\Attributes as OA; | |||||||
|         'description' => ['type' => 'string'], |         'description' => ['type' => 'string'], | ||||||
|     ] |     ] | ||||||
| )] | )] | ||||||
| class Environment extends Model | class Environment extends BaseModel | ||||||
| { | { | ||||||
|     protected $guarded = []; |     protected $guarded = []; | ||||||
| 
 | 
 | ||||||
|   | |||||||
| @@ -14,9 +14,8 @@ use Visus\Cuid2\Cuid2; | |||||||
|     properties: [ |     properties: [ | ||||||
|         'id' => ['type' => 'integer'], |         'id' => ['type' => 'integer'], | ||||||
|         'uuid' => ['type' => 'string'], |         'uuid' => ['type' => 'string'], | ||||||
|         'application_id' => ['type' => 'integer'], |         'resourceable_type' => ['type' => 'string'], | ||||||
|         'service_id' => ['type' => 'integer'], |         'resourceable_id' => ['type' => 'integer'], | ||||||
|         'database_id' => ['type' => 'integer'], |  | ||||||
|         'is_build_time' => ['type' => 'boolean'], |         'is_build_time' => ['type' => 'boolean'], | ||||||
|         'is_literal' => ['type' => 'boolean'], |         'is_literal' => ['type' => 'boolean'], | ||||||
|         'is_multiline' => ['type' => 'boolean'], |         'is_multiline' => ['type' => 'boolean'], | ||||||
| @@ -42,6 +41,8 @@ class EnvironmentVariable extends Model | |||||||
|         'is_multiline' => 'boolean', |         'is_multiline' => 'boolean', | ||||||
|         'is_preview' => 'boolean', |         'is_preview' => 'boolean', | ||||||
|         'version' => 'string', |         'version' => 'string', | ||||||
|  |         'resourceable_type' => 'string', | ||||||
|  |         'resourceable_id' => 'integer', | ||||||
|     ]; |     ]; | ||||||
| 
 | 
 | ||||||
|     protected $appends = ['real_value', 'is_shared', 'is_really_required']; |     protected $appends = ['real_value', 'is_shared', 'is_really_required']; | ||||||
| @@ -53,18 +54,25 @@ class EnvironmentVariable extends Model | |||||||
|                 $model->uuid = (string) new Cuid2; |                 $model->uuid = (string) new Cuid2; | ||||||
|             } |             } | ||||||
|         }); |         }); | ||||||
|  | 
 | ||||||
|         static::created(function (EnvironmentVariable $environment_variable) { |         static::created(function (EnvironmentVariable $environment_variable) { | ||||||
|             if ($environment_variable->application_id && ! $environment_variable->is_preview) { |             if ($environment_variable->resourceable_type === Application::class && ! $environment_variable->is_preview) { | ||||||
|                 $found = ModelsEnvironmentVariable::where('key', $environment_variable->key)->where('application_id', $environment_variable->application_id)->where('is_preview', true)->first(); |                 $found = ModelsEnvironmentVariable::where('key', $environment_variable->key) | ||||||
|  |                     ->where('resourceable_type', Application::class) | ||||||
|  |                     ->where('resourceable_id', $environment_variable->resourceable_id) | ||||||
|  |                     ->where('is_preview', true) | ||||||
|  |                     ->first(); | ||||||
|  | 
 | ||||||
|                 if (! $found) { |                 if (! $found) { | ||||||
|                     $application = Application::find($environment_variable->application_id); |                     $application = Application::find($environment_variable->resourceable_id); | ||||||
|                     if ($application->build_pack !== 'dockerfile') { |                     if ($application && $application->build_pack !== 'dockerfile') { | ||||||
|                         ModelsEnvironmentVariable::create([ |                         ModelsEnvironmentVariable::create([ | ||||||
|                             'key' => $environment_variable->key, |                             'key' => $environment_variable->key, | ||||||
|                             'value' => $environment_variable->value, |                             'value' => $environment_variable->value, | ||||||
|                             'is_build_time' => $environment_variable->is_build_time, |                             'is_build_time' => $environment_variable->is_build_time, | ||||||
|                             'is_multiline' => $environment_variable->is_multiline ?? false, |                             'is_multiline' => $environment_variable->is_multiline ?? false, | ||||||
|                             'application_id' => $environment_variable->application_id, |                             'resourceable_type' => Application::class, | ||||||
|  |                             'resourceable_id' => $environment_variable->resourceable_id, | ||||||
|                             'is_preview' => true, |                             'is_preview' => true, | ||||||
|                         ]); |                         ]); | ||||||
|                     } |                     } | ||||||
| @@ -74,6 +82,7 @@ class EnvironmentVariable extends Model | |||||||
|                 'version' => config('constants.coolify.version'), |                 'version' => config('constants.coolify.version'), | ||||||
|             ]); |             ]); | ||||||
|         }); |         }); | ||||||
|  | 
 | ||||||
|         static::saving(function (EnvironmentVariable $environmentVariable) { |         static::saving(function (EnvironmentVariable $environmentVariable) { | ||||||
|             $environmentVariable->updateIsShared(); |             $environmentVariable->updateIsShared(); | ||||||
|         }); |         }); | ||||||
| @@ -92,43 +101,32 @@ class EnvironmentVariable extends Model | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * Get the parent resourceable model. | ||||||
|  |      */ | ||||||
|  |     public function resourceable() | ||||||
|  |     { | ||||||
|  |         return $this->morphTo(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function resource() |     public function resource() | ||||||
|     { |     { | ||||||
|         $resource = null; |         return $this->resourceable; | ||||||
|         if ($this->application_id) { |  | ||||||
|             $resource = Application::find($this->application_id); |  | ||||||
|         } elseif ($this->service_id) { |  | ||||||
|             $resource = Service::find($this->service_id); |  | ||||||
|         } elseif ($this->standalone_postgresql_id) { |  | ||||||
|             $resource = StandalonePostgresql::find($this->standalone_postgresql_id); |  | ||||||
|         } elseif ($this->standalone_redis_id) { |  | ||||||
|             $resource = StandaloneRedis::find($this->standalone_redis_id); |  | ||||||
|         } elseif ($this->standalone_mongodb_id) { |  | ||||||
|             $resource = StandaloneMongodb::find($this->standalone_mongodb_id); |  | ||||||
|         } elseif ($this->standalone_mysql_id) { |  | ||||||
|             $resource = StandaloneMysql::find($this->standalone_mysql_id); |  | ||||||
|         } elseif ($this->standalone_mariadb_id) { |  | ||||||
|             $resource = StandaloneMariadb::find($this->standalone_mariadb_id); |  | ||||||
|         } elseif ($this->standalone_keydb_id) { |  | ||||||
|             $resource = StandaloneKeydb::find($this->standalone_keydb_id); |  | ||||||
|         } elseif ($this->standalone_dragonfly_id) { |  | ||||||
|             $resource = StandaloneDragonfly::find($this->standalone_dragonfly_id); |  | ||||||
|         } elseif ($this->standalone_clickhouse_id) { |  | ||||||
|             $resource = StandaloneClickhouse::find($this->standalone_clickhouse_id); |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return $resource; |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function realValue(): Attribute |     public function realValue(): Attribute | ||||||
|     { |     { | ||||||
|         $resource = $this->resource(); |  | ||||||
| 
 |  | ||||||
|         return Attribute::make( |         return Attribute::make( | ||||||
|             get: function () use ($resource) { |             get: function () { | ||||||
|                 $env = $this->get_real_environment_variables($this->value, $resource); |                 if (! $this->relationLoaded('resourceable')) { | ||||||
|  |                     $this->load('resourceable'); | ||||||
|  |                 } | ||||||
|  |                 $resource = $this->resourceable; | ||||||
|  |                 if (! $resource) { | ||||||
|  |                     return null; | ||||||
|  |                 } | ||||||
| 
 | 
 | ||||||
|                 return data_get($env, 'value', $env); |                 return $this->get_real_environment_variables($this->value, $resource); | ||||||
|             } |             } | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| @@ -164,7 +162,6 @@ class EnvironmentVariable extends Model | |||||||
|         if ($sharedEnvsFound->isEmpty()) { |         if ($sharedEnvsFound->isEmpty()) { | ||||||
|             return $environment_variable; |             return $environment_variable; | ||||||
|         } |         } | ||||||
| 
 |  | ||||||
|         foreach ($sharedEnvsFound as $sharedEnv) { |         foreach ($sharedEnvsFound as $sharedEnv) { | ||||||
|             $type = str($sharedEnv)->match('/(.*?)\./'); |             $type = str($sharedEnv)->match('/(.*?)\./'); | ||||||
|             if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) { |             if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) { | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
| namespace App\Models; | namespace App\Models; | ||||||
| 
 | 
 | ||||||
| use OpenApi\Attributes as OA; | use OpenApi\Attributes as OA; | ||||||
|  | use Visus\Cuid2\Cuid2; | ||||||
| 
 | 
 | ||||||
| #[OA\Schema(
 | #[OA\Schema(
 | ||||||
|     description: 'Project model', |     description: 'Project model', | ||||||
| @@ -24,8 +25,6 @@ class Project extends BaseModel | |||||||
| { | { | ||||||
|     protected $guarded = []; |     protected $guarded = []; | ||||||
| 
 | 
 | ||||||
|     protected $appends = ['default_environment']; |  | ||||||
| 
 |  | ||||||
|     public static function ownedByCurrentTeam() |     public static function ownedByCurrentTeam() | ||||||
|     { |     { | ||||||
|         return Project::whereTeamId(currentTeam()->id)->orderByRaw('LOWER(name)'); |         return Project::whereTeamId(currentTeam()->id)->orderByRaw('LOWER(name)'); | ||||||
| @@ -40,6 +39,7 @@ class Project extends BaseModel | |||||||
|             Environment::create([ |             Environment::create([ | ||||||
|                 'name' => 'production', |                 'name' => 'production', | ||||||
|                 'project_id' => $project->id, |                 'project_id' => $project->id, | ||||||
|  |                 'uuid' => (string) new Cuid2, | ||||||
|             ]); |             ]); | ||||||
|         }); |         }); | ||||||
|         static::deleting(function ($project) { |         static::deleting(function ($project) { | ||||||
| @@ -140,18 +140,4 @@ class Project extends BaseModel | |||||||
|     { |     { | ||||||
|         return $this->postgresqls()->get()->merge($this->redis()->get())->merge($this->mongodbs()->get())->merge($this->mysqls()->get())->merge($this->mariadbs()->get())->merge($this->keydbs()->get())->merge($this->dragonflies()->get())->merge($this->clickhouses()->get()); |         return $this->postgresqls()->get()->merge($this->redis()->get())->merge($this->mongodbs()->get())->merge($this->mysqls()->get())->merge($this->mariadbs()->get())->merge($this->keydbs()->get())->merge($this->dragonflies()->get())->merge($this->clickhouses()->get()); | ||||||
|     } |     } | ||||||
| 
 |  | ||||||
|     public function getDefaultEnvironmentAttribute() |  | ||||||
|     { |  | ||||||
|         $default = $this->environments()->where('name', 'production')->first(); |  | ||||||
|         if ($default) { |  | ||||||
|             return $default->name; |  | ||||||
|         } |  | ||||||
|         $default = $this->environments()->get(); |  | ||||||
|         if ($default->count() > 0) { |  | ||||||
|             return $default->sortBy('created_at')->first()->name; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         return null; |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -54,6 +54,8 @@ class Server extends BaseModel | |||||||
| 
 | 
 | ||||||
|     public static $batch_counter = 0; |     public static $batch_counter = 0; | ||||||
| 
 | 
 | ||||||
|  |     protected $appends = ['is_coolify_host']; | ||||||
|  | 
 | ||||||
|     protected static function booted() |     protected static function booted() | ||||||
|     { |     { | ||||||
|         static::saving(function ($server) { |         static::saving(function ($server) { | ||||||
| @@ -156,6 +158,15 @@ class Server extends BaseModel | |||||||
|         return 'server'; |         return 'server'; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     protected function isCoolifyHost(): Attribute | ||||||
|  |     { | ||||||
|  |         return Attribute::make( | ||||||
|  |             get: function () { | ||||||
|  |                 return $this->id === 0; | ||||||
|  |             } | ||||||
|  |         ); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public static function isReachable() |     public static function isReachable() | ||||||
|     { |     { | ||||||
|         return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true); |         return Server::ownedByCurrentTeam()->whereRelation('settings', 'is_reachable', true); | ||||||
| @@ -656,9 +667,9 @@ $schema://$host { | |||||||
|         $containers = collect([]); |         $containers = collect([]); | ||||||
|         $containerReplicates = collect([]); |         $containerReplicates = collect([]); | ||||||
|         if ($this->isSwarm()) { |         if ($this->isSwarm()) { | ||||||
|             $containers = instant_remote_process(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this, false); |             $containers = instant_remote_process_with_timeout(["docker service inspect $(docker service ls -q) --format '{{json .}}'"], $this, false); | ||||||
|             $containers = format_docker_command_output_to_json($containers); |             $containers = format_docker_command_output_to_json($containers); | ||||||
|             $containerReplicates = instant_remote_process(["docker service ls --format '{{json .}}'"], $this, false); |             $containerReplicates = instant_remote_process_with_timeout(["docker service ls --format '{{json .}}'"], $this, false); | ||||||
|             if ($containerReplicates) { |             if ($containerReplicates) { | ||||||
|                 $containerReplicates = format_docker_command_output_to_json($containerReplicates); |                 $containerReplicates = format_docker_command_output_to_json($containerReplicates); | ||||||
|                 foreach ($containerReplicates as $containerReplica) { |                 foreach ($containerReplicates as $containerReplica) { | ||||||
| @@ -682,7 +693,7 @@ $schema://$host { | |||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             $containers = instant_remote_process(["docker container inspect $(docker container ls -aq) --format '{{json .}}'"], $this, false); |             $containers = instant_remote_process_with_timeout(["docker container inspect $(docker container ls -aq) --format '{{json .}}'"], $this, false); | ||||||
|             $containers = format_docker_command_output_to_json($containers); |             $containers = format_docker_command_output_to_json($containers); | ||||||
|             $containerReplicates = collect([]); |             $containerReplicates = collect([]); | ||||||
|         } |         } | ||||||
|   | |||||||
| @@ -1050,10 +1050,11 @@ class Service extends BaseModel | |||||||
|                     $fields->put('MySQL', $data->toArray()); |                     $fields->put('MySQL', $data->toArray()); | ||||||
|                     break; |                     break; | ||||||
|                 case $image->contains('mariadb'): |                 case $image->contains('mariadb'): | ||||||
|                     $userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', '_APP_DB_USER', 'SERVICE_USER_MYSQL', 'MYSQL_USER']; |                     $userVariables = ['SERVICE_USER_MARIADB', 'SERVICE_USER_WORDPRESS', 'SERVICE_USER_MYSQL', 'MYSQL_USER']; | ||||||
|                     $passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS', 'MYSQL_PASSWORD']; |                     $passwordVariables = ['SERVICE_PASSWORD_MARIADB', 'SERVICE_PASSWORD_WORDPRESS', '_APP_DB_PASS', 'MYSQL_PASSWORD']; | ||||||
|                     $rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS', 'MYSQL_ROOT_PASSWORD']; |                     $rootPasswordVariables = ['SERVICE_PASSWORD_MARIADBROOT', 'SERVICE_PASSWORD_ROOT', '_APP_DB_ROOT_PASS', 'MYSQL_ROOT_PASSWORD']; | ||||||
|                     $dbNameVariables = ['SERVICE_DATABASE_MARIADB', 'SERVICE_DATABASE_WORDPRESS', '_APP_DB_SCHEMA', 'MYSQL_DATABASE']; |                     $dbNameVariables = ['SERVICE_DATABASE_MARIADB', 'SERVICE_DATABASE_WORDPRESS', '_APP_DB_SCHEMA', 'MYSQL_DATABASE']; | ||||||
|  | 
 | ||||||
|                     $mariadb_user = $this->environment_variables()->whereIn('key', $userVariables)->first(); |                     $mariadb_user = $this->environment_variables()->whereIn('key', $userVariables)->first(); | ||||||
|                     $mariadb_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first(); |                     $mariadb_password = $this->environment_variables()->whereIn('key', $passwordVariables)->first(); | ||||||
|                     $mariadb_root_password = $this->environment_variables()->whereIn('key', $rootPasswordVariables)->first(); |                     $mariadb_root_password = $this->environment_variables()->whereIn('key', $rootPasswordVariables)->first(); | ||||||
| @@ -1102,6 +1103,23 @@ class Service extends BaseModel | |||||||
|                     break; |                     break; | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |         $fields = collect($fields)->map(function ($extraFields) { | ||||||
|  |             if (is_array($extraFields)) { | ||||||
|  |                 $extraFields = collect($extraFields)->map(function ($field) { | ||||||
|  |                     if (filled($field['value']) && str($field['value'])->startsWith('$SERVICE_')) { | ||||||
|  |                         $searchValue = str($field['value'])->after('$')->value; | ||||||
|  |                         $newValue = $this->environment_variables()->where('key', $searchValue)->first(); | ||||||
|  |                         if ($newValue) { | ||||||
|  |                             $field['value'] = $newValue->value; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|  |                     return $field; | ||||||
|  |                 }); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return $extraFields; | ||||||
|  |         }); | ||||||
| 
 | 
 | ||||||
|         return $fields; |         return $fields; | ||||||
|     } |     } | ||||||
| @@ -1120,7 +1138,8 @@ class Service extends BaseModel | |||||||
|                     'key' => $key, |                     'key' => $key, | ||||||
|                     'value' => $value, |                     'value' => $value, | ||||||
|                     'is_build_time' => false, |                     'is_build_time' => false, | ||||||
|                     'service_id' => $this->id, |                     'resourceable_id' => $this->id, | ||||||
|  |                     'resourceable_type' => $this->getMorphClass(), | ||||||
|                     'is_preview' => false, |                     'is_preview' => false, | ||||||
|                 ]); |                 ]); | ||||||
|             } |             } | ||||||
| @@ -1132,7 +1151,7 @@ class Service extends BaseModel | |||||||
|         if (data_get($this, 'environment.project.uuid')) { |         if (data_get($this, 'environment.project.uuid')) { | ||||||
|             return route('project.service.configuration', [ |             return route('project.service.configuration', [ | ||||||
|                 'project_uuid' => data_get($this, 'environment.project.uuid'), |                 'project_uuid' => data_get($this, 'environment.project.uuid'), | ||||||
|                 'environment_name' => data_get($this, 'environment.name'), |                 'environment_uuid' => data_get($this, 'environment.uuid'), | ||||||
|                 'service_uuid' => data_get($this, 'uuid'), |                 'service_uuid' => data_get($this, 'uuid'), | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
| @@ -1145,7 +1164,7 @@ class Service extends BaseModel | |||||||
|         if (data_get($this, 'environment.project.uuid')) { |         if (data_get($this, 'environment.project.uuid')) { | ||||||
|             $route = route('project.service.scheduled-tasks', [ |             $route = route('project.service.scheduled-tasks', [ | ||||||
|                 'project_uuid' => data_get($this, 'environment.project.uuid'), |                 'project_uuid' => data_get($this, 'environment.project.uuid'), | ||||||
|                 'environment_name' => data_get($this, 'environment.name'), |                 'environment_uuid' => data_get($this, 'environment.uuid'), | ||||||
|                 'service_uuid' => data_get($this, 'uuid'), |                 'service_uuid' => data_get($this, 'uuid'), | ||||||
|                 'task_uuid' => $task_uuid, |                 'task_uuid' => $task_uuid, | ||||||
|             ]); |             ]); | ||||||
| @@ -1232,14 +1251,17 @@ class Service extends BaseModel | |||||||
|         return $this->hasMany(ScheduledTask::class)->orderBy('name', 'asc'); |         return $this->hasMany(ScheduledTask::class)->orderBy('name', 'asc'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function environment_variables(): HasMany |     public function environment_variables() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class)->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC"); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->orderBy('key', 'asc'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function environment_variables_preview(): HasMany |     public function environment_variables_preview() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class)->where('is_preview', true)->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC"); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->where('is_preview', true) | ||||||
|  |             ->orderByRaw("LOWER(key) LIKE LOWER('SERVICE%') DESC, LOWER(key) ASC"); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function workdir() |     public function workdir() | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ namespace App\Models; | |||||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | use Illuminate\Database\Eloquent\Casts\Attribute; | ||||||
| use Illuminate\Database\Eloquent\Collection; | use Illuminate\Database\Eloquent\Collection; | ||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
| use Illuminate\Database\Eloquent\Relations\HasMany; |  | ||||||
| use Illuminate\Database\Eloquent\SoftDeletes; | use Illuminate\Database\Eloquent\SoftDeletes; | ||||||
| 
 | 
 | ||||||
| class StandaloneClickhouse extends BaseModel | class StandaloneClickhouse extends BaseModel | ||||||
| @@ -169,7 +168,7 @@ class StandaloneClickhouse extends BaseModel | |||||||
|         if (data_get($this, 'environment.project.uuid')) { |         if (data_get($this, 'environment.project.uuid')) { | ||||||
|             return route('project.database.configuration', [ |             return route('project.database.configuration', [ | ||||||
|                 'project_uuid' => data_get($this, 'environment.project.uuid'), |                 'project_uuid' => data_get($this, 'environment.project.uuid'), | ||||||
|                 'environment_name' => data_get($this, 'environment.name'), |                 'environment_uuid' => data_get($this, 'environment.uuid'), | ||||||
|                 'database_uuid' => data_get($this, 'uuid'), |                 'database_uuid' => data_get($this, 'uuid'), | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
| @@ -251,14 +250,15 @@ class StandaloneClickhouse extends BaseModel | |||||||
|         return $this->morphTo(); |         return $this->morphTo(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function environment_variables(): HasMany |     public function environment_variables() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->orderBy('key', 'asc'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function runtime_environment_variables(): HasMany |     public function runtime_environment_variables() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function persistentStorages() |     public function persistentStorages() | ||||||
|   | |||||||
| @@ -6,6 +6,19 @@ class StandaloneDocker extends BaseModel | |||||||
| { | { | ||||||
|     protected $guarded = []; |     protected $guarded = []; | ||||||
| 
 | 
 | ||||||
|  |     protected static function boot() | ||||||
|  |     { | ||||||
|  |         parent::boot(); | ||||||
|  |         static::created(function ($newStandaloneDocker) { | ||||||
|  |             $server = $newStandaloneDocker->server; | ||||||
|  |             instant_remote_process([ | ||||||
|  |                 "docker network inspect $newStandaloneDocker->network >/dev/null 2>&1 || docker network create --driver overlay --attachable $newStandaloneDocker->network >/dev/null", | ||||||
|  |             ], $server, false); | ||||||
|  |             $connectProxyToDockerNetworks = connectProxyToNetworks($server); | ||||||
|  |             instant_remote_process($connectProxyToDockerNetworks, $server, false); | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     public function applications() |     public function applications() | ||||||
|     { |     { | ||||||
|         return $this->morphMany(Application::class, 'destination'); |         return $this->morphMany(Application::class, 'destination'); | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ namespace App\Models; | |||||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | use Illuminate\Database\Eloquent\Casts\Attribute; | ||||||
| use Illuminate\Database\Eloquent\Collection; | use Illuminate\Database\Eloquent\Collection; | ||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
| use Illuminate\Database\Eloquent\Relations\HasMany; |  | ||||||
| use Illuminate\Database\Eloquent\SoftDeletes; | use Illuminate\Database\Eloquent\SoftDeletes; | ||||||
| 
 | 
 | ||||||
| class StandaloneDragonfly extends BaseModel | class StandaloneDragonfly extends BaseModel | ||||||
| @@ -174,7 +173,7 @@ class StandaloneDragonfly extends BaseModel | |||||||
|         if (data_get($this, 'environment.project.uuid')) { |         if (data_get($this, 'environment.project.uuid')) { | ||||||
|             return route('project.database.configuration', [ |             return route('project.database.configuration', [ | ||||||
|                 'project_uuid' => data_get($this, 'environment.project.uuid'), |                 'project_uuid' => data_get($this, 'environment.project.uuid'), | ||||||
|                 'environment_name' => data_get($this, 'environment.name'), |                 'environment_uuid' => data_get($this, 'environment.uuid'), | ||||||
|                 'database_uuid' => data_get($this, 'uuid'), |                 'database_uuid' => data_get($this, 'uuid'), | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
| @@ -251,14 +250,9 @@ class StandaloneDragonfly extends BaseModel | |||||||
|         return $this->morphTo(); |         return $this->morphTo(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function environment_variables(): HasMany |     public function runtime_environment_variables() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable'); | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function runtime_environment_variables(): HasMany |  | ||||||
|     { |  | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function persistentStorages() |     public function persistentStorages() | ||||||
| @@ -319,4 +313,10 @@ class StandaloneDragonfly extends BaseModel | |||||||
|     { |     { | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function environment_variables() | ||||||
|  |     { | ||||||
|  |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->orderBy('key', 'asc'); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ namespace App\Models; | |||||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | use Illuminate\Database\Eloquent\Casts\Attribute; | ||||||
| use Illuminate\Database\Eloquent\Collection; | use Illuminate\Database\Eloquent\Collection; | ||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
| use Illuminate\Database\Eloquent\Relations\HasMany; |  | ||||||
| use Illuminate\Database\Eloquent\SoftDeletes; | use Illuminate\Database\Eloquent\SoftDeletes; | ||||||
| 
 | 
 | ||||||
| class StandaloneKeydb extends BaseModel | class StandaloneKeydb extends BaseModel | ||||||
| @@ -174,7 +173,7 @@ class StandaloneKeydb extends BaseModel | |||||||
|         if (data_get($this, 'environment.project.uuid')) { |         if (data_get($this, 'environment.project.uuid')) { | ||||||
|             return route('project.database.configuration', [ |             return route('project.database.configuration', [ | ||||||
|                 'project_uuid' => data_get($this, 'environment.project.uuid'), |                 'project_uuid' => data_get($this, 'environment.project.uuid'), | ||||||
|                 'environment_name' => data_get($this, 'environment.name'), |                 'environment_uuid' => data_get($this, 'environment.uuid'), | ||||||
|                 'database_uuid' => data_get($this, 'uuid'), |                 'database_uuid' => data_get($this, 'uuid'), | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
| @@ -251,14 +250,9 @@ class StandaloneKeydb extends BaseModel | |||||||
|         return $this->morphTo(); |         return $this->morphTo(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function environment_variables(): HasMany |     public function runtime_environment_variables() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable'); | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function runtime_environment_variables(): HasMany |  | ||||||
|     { |  | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function persistentStorages() |     public function persistentStorages() | ||||||
| @@ -319,4 +313,10 @@ class StandaloneKeydb extends BaseModel | |||||||
|     { |     { | ||||||
|         return false; |         return false; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function environment_variables() | ||||||
|  |     { | ||||||
|  |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->orderBy('key', 'asc'); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ namespace App\Models; | |||||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | use Illuminate\Database\Eloquent\Casts\Attribute; | ||||||
| use Illuminate\Database\Eloquent\Collection; | use Illuminate\Database\Eloquent\Collection; | ||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
| use Illuminate\Database\Eloquent\Relations\HasMany; |  | ||||||
| use Illuminate\Database\Eloquent\SoftDeletes; | use Illuminate\Database\Eloquent\SoftDeletes; | ||||||
| 
 | 
 | ||||||
| class StandaloneMariadb extends BaseModel | class StandaloneMariadb extends BaseModel | ||||||
| @@ -174,7 +173,7 @@ class StandaloneMariadb extends BaseModel | |||||||
|         if (data_get($this, 'environment.project.uuid')) { |         if (data_get($this, 'environment.project.uuid')) { | ||||||
|             return route('project.database.configuration', [ |             return route('project.database.configuration', [ | ||||||
|                 'project_uuid' => data_get($this, 'environment.project.uuid'), |                 'project_uuid' => data_get($this, 'environment.project.uuid'), | ||||||
|                 'environment_name' => data_get($this, 'environment.name'), |                 'environment_uuid' => data_get($this, 'environment.uuid'), | ||||||
|                 'database_uuid' => data_get($this, 'uuid'), |                 'database_uuid' => data_get($this, 'uuid'), | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
| @@ -251,14 +250,15 @@ class StandaloneMariadb extends BaseModel | |||||||
|         return $this->morphTo(); |         return $this->morphTo(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function environment_variables(): HasMany |     public function environment_variables() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->orderBy('key', 'asc'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function runtime_environment_variables(): HasMany |     public function runtime_environment_variables() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function persistentStorages() |     public function persistentStorages() | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ namespace App\Models; | |||||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | use Illuminate\Database\Eloquent\Casts\Attribute; | ||||||
| use Illuminate\Database\Eloquent\Collection; | use Illuminate\Database\Eloquent\Collection; | ||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
| use Illuminate\Database\Eloquent\Relations\HasMany; |  | ||||||
| use Illuminate\Database\Eloquent\SoftDeletes; | use Illuminate\Database\Eloquent\SoftDeletes; | ||||||
| 
 | 
 | ||||||
| class StandaloneMongodb extends BaseModel | class StandaloneMongodb extends BaseModel | ||||||
| @@ -183,7 +182,7 @@ class StandaloneMongodb extends BaseModel | |||||||
|         if (data_get($this, 'environment.project.uuid')) { |         if (data_get($this, 'environment.project.uuid')) { | ||||||
|             return route('project.database.configuration', [ |             return route('project.database.configuration', [ | ||||||
|                 'project_uuid' => data_get($this, 'environment.project.uuid'), |                 'project_uuid' => data_get($this, 'environment.project.uuid'), | ||||||
|                 'environment_name' => data_get($this, 'environment.name'), |                 'environment_uuid' => data_get($this, 'environment.uuid'), | ||||||
|                 'database_uuid' => data_get($this, 'uuid'), |                 'database_uuid' => data_get($this, 'uuid'), | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
| @@ -271,14 +270,9 @@ class StandaloneMongodb extends BaseModel | |||||||
|         return $this->morphTo(); |         return $this->morphTo(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function environment_variables(): HasMany |     public function runtime_environment_variables() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable'); | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function runtime_environment_variables(): HasMany |  | ||||||
|     { |  | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function persistentStorages() |     public function persistentStorages() | ||||||
| @@ -339,4 +333,10 @@ class StandaloneMongodb extends BaseModel | |||||||
|     { |     { | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function environment_variables() | ||||||
|  |     { | ||||||
|  |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->orderBy('key', 'asc'); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ namespace App\Models; | |||||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | use Illuminate\Database\Eloquent\Casts\Attribute; | ||||||
| use Illuminate\Database\Eloquent\Collection; | use Illuminate\Database\Eloquent\Collection; | ||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
| use Illuminate\Database\Eloquent\Relations\HasMany; |  | ||||||
| use Illuminate\Database\Eloquent\SoftDeletes; | use Illuminate\Database\Eloquent\SoftDeletes; | ||||||
| 
 | 
 | ||||||
| class StandaloneMysql extends BaseModel | class StandaloneMysql extends BaseModel | ||||||
| @@ -175,7 +174,7 @@ class StandaloneMysql extends BaseModel | |||||||
|         if (data_get($this, 'environment.project.uuid')) { |         if (data_get($this, 'environment.project.uuid')) { | ||||||
|             return route('project.database.configuration', [ |             return route('project.database.configuration', [ | ||||||
|                 'project_uuid' => data_get($this, 'environment.project.uuid'), |                 'project_uuid' => data_get($this, 'environment.project.uuid'), | ||||||
|                 'environment_name' => data_get($this, 'environment.name'), |                 'environment_uuid' => data_get($this, 'environment.uuid'), | ||||||
|                 'database_uuid' => data_get($this, 'uuid'), |                 'database_uuid' => data_get($this, 'uuid'), | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
| @@ -252,14 +251,9 @@ class StandaloneMysql extends BaseModel | |||||||
|         return $this->morphTo(); |         return $this->morphTo(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function environment_variables(): HasMany |     public function runtime_environment_variables() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable'); | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function runtime_environment_variables(): HasMany |  | ||||||
|     { |  | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function persistentStorages() |     public function persistentStorages() | ||||||
| @@ -320,4 +314,10 @@ class StandaloneMysql extends BaseModel | |||||||
|     { |     { | ||||||
|         return true; |         return true; | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function environment_variables() | ||||||
|  |     { | ||||||
|  |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->orderBy('key', 'asc'); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ namespace App\Models; | |||||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | use Illuminate\Database\Eloquent\Casts\Attribute; | ||||||
| use Illuminate\Database\Eloquent\Collection; | use Illuminate\Database\Eloquent\Collection; | ||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
| use Illuminate\Database\Eloquent\Relations\HasMany; |  | ||||||
| use Illuminate\Database\Eloquent\SoftDeletes; | use Illuminate\Database\Eloquent\SoftDeletes; | ||||||
| 
 | 
 | ||||||
| class StandalonePostgresql extends BaseModel | class StandalonePostgresql extends BaseModel | ||||||
| @@ -170,7 +169,7 @@ class StandalonePostgresql extends BaseModel | |||||||
|         if (data_get($this, 'environment.project.uuid')) { |         if (data_get($this, 'environment.project.uuid')) { | ||||||
|             return route('project.database.configuration', [ |             return route('project.database.configuration', [ | ||||||
|                 'project_uuid' => data_get($this, 'environment.project.uuid'), |                 'project_uuid' => data_get($this, 'environment.project.uuid'), | ||||||
|                 'environment_name' => data_get($this, 'environment.name'), |                 'environment_uuid' => data_get($this, 'environment.uuid'), | ||||||
|                 'database_uuid' => data_get($this, 'uuid'), |                 'database_uuid' => data_get($this, 'uuid'), | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
| @@ -252,14 +251,9 @@ class StandalonePostgresql extends BaseModel | |||||||
|         return $this->morphTo(); |         return $this->morphTo(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function environment_variables(): HasMany |     public function runtime_environment_variables() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable'); | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function runtime_environment_variables(): HasMany |  | ||||||
|     { |  | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function persistentStorages() |     public function persistentStorages() | ||||||
| @@ -320,4 +314,10 @@ class StandalonePostgresql extends BaseModel | |||||||
| 
 | 
 | ||||||
|         return $parsedCollection->toArray(); |         return $parsedCollection->toArray(); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function environment_variables() | ||||||
|  |     { | ||||||
|  |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->orderBy('key', 'asc'); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ namespace App\Models; | |||||||
| use Illuminate\Database\Eloquent\Casts\Attribute; | use Illuminate\Database\Eloquent\Casts\Attribute; | ||||||
| use Illuminate\Database\Eloquent\Collection; | use Illuminate\Database\Eloquent\Collection; | ||||||
| use Illuminate\Database\Eloquent\Factories\HasFactory; | use Illuminate\Database\Eloquent\Factories\HasFactory; | ||||||
| use Illuminate\Database\Eloquent\Relations\HasMany; |  | ||||||
| use Illuminate\Database\Eloquent\SoftDeletes; | use Illuminate\Database\Eloquent\SoftDeletes; | ||||||
| 
 | 
 | ||||||
| class StandaloneRedis extends BaseModel | class StandaloneRedis extends BaseModel | ||||||
| @@ -170,7 +169,7 @@ class StandaloneRedis extends BaseModel | |||||||
|         if (data_get($this, 'environment.project.uuid')) { |         if (data_get($this, 'environment.project.uuid')) { | ||||||
|             return route('project.database.configuration', [ |             return route('project.database.configuration', [ | ||||||
|                 'project_uuid' => data_get($this, 'environment.project.uuid'), |                 'project_uuid' => data_get($this, 'environment.project.uuid'), | ||||||
|                 'environment_name' => data_get($this, 'environment.name'), |                 'environment_uuid' => data_get($this, 'environment.uuid'), | ||||||
|                 'database_uuid' => data_get($this, 'uuid'), |                 'database_uuid' => data_get($this, 'uuid'), | ||||||
|             ]); |             ]); | ||||||
|         } |         } | ||||||
| @@ -262,14 +261,9 @@ class StandaloneRedis extends BaseModel | |||||||
|         return $this->morphTo(); |         return $this->morphTo(); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function environment_variables(): HasMany |     public function runtime_environment_variables() | ||||||
|     { |     { | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |         return $this->morphMany(EnvironmentVariable::class, 'resourceable'); | ||||||
|     } |  | ||||||
| 
 |  | ||||||
|     public function runtime_environment_variables(): HasMany |  | ||||||
|     { |  | ||||||
|         return $this->hasMany(EnvironmentVariable::class); |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function persistentStorages() |     public function persistentStorages() | ||||||
| @@ -359,4 +353,10 @@ class StandaloneRedis extends BaseModel | |||||||
|             } |             } | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  | 
 | ||||||
|  |     public function environment_variables() | ||||||
|  |     { | ||||||
|  |         return $this->morphMany(EnvironmentVariable::class, 'resourceable') | ||||||
|  |             ->orderBy('key', 'asc'); | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -120,22 +120,10 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen | |||||||
|         return Attribute::make( |         return Attribute::make( | ||||||
|             get: function () { |             get: function () { | ||||||
|                 if (config('constants.coolify.self_hosted') || $this->id === 0) { |                 if (config('constants.coolify.self_hosted') || $this->id === 0) { | ||||||
|                     $subscription = 'self-hosted'; |                     return 999999999999; | ||||||
|                 } else { |  | ||||||
|                     $subscription = data_get($this, 'subscription'); |  | ||||||
|                     if (is_null($subscription)) { |  | ||||||
|                         $subscription = 'zero'; |  | ||||||
|                     } else { |  | ||||||
|                         $subscription = $subscription->type(); |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|                 if ($this->custom_server_limit) { |  | ||||||
|                     $serverLimit = $this->custom_server_limit; |  | ||||||
|                 } else { |  | ||||||
|                     $serverLimit = config('constants.limits.server')[strtolower($subscription)]; |  | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 return $serverLimit ?? 2; |                 return $this->custom_server_limit ?? 2; | ||||||
|             } |             } | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
| @@ -259,8 +247,17 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen | |||||||
|     public function sources() |     public function sources() | ||||||
|     { |     { | ||||||
|         $sources = collect([]); |         $sources = collect([]); | ||||||
|         $github_apps = $this->hasMany(GithubApp::class)->whereisPublic(false)->get(); |         $github_apps = GithubApp::where(function ($query) { | ||||||
|         $gitlab_apps = $this->hasMany(GitlabApp::class)->whereisPublic(false)->get(); |             $query->where('team_id', $this->id) | ||||||
|  |                 ->Where('is_public', false) | ||||||
|  |                 ->orWhere('is_system_wide', true); | ||||||
|  |         })->get(); | ||||||
|  | 
 | ||||||
|  |         $gitlab_apps = GitlabApp::where(function ($query) { | ||||||
|  |             $query->where('team_id', $this->id) | ||||||
|  |                 ->Where('is_public', false) | ||||||
|  |                 ->orWhere('is_system_wide', true); | ||||||
|  |         })->get(); | ||||||
| 
 | 
 | ||||||
|         return $sources->merge($github_apps)->merge($gitlab_apps); |         return $sources->merge($github_apps)->merge($gitlab_apps); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -22,6 +22,8 @@ class DeploymentFailed extends CustomEmailNotification | |||||||
| 
 | 
 | ||||||
|     public string $project_uuid; |     public string $project_uuid; | ||||||
| 
 | 
 | ||||||
|  |     public string $environment_uuid; | ||||||
|  | 
 | ||||||
|     public string $environment_name; |     public string $environment_name; | ||||||
| 
 | 
 | ||||||
|     public ?string $deployment_url = null; |     public ?string $deployment_url = null; | ||||||
| @@ -36,12 +38,13 @@ class DeploymentFailed extends CustomEmailNotification | |||||||
|         $this->preview = $preview; |         $this->preview = $preview; | ||||||
|         $this->application_name = data_get($application, 'name'); |         $this->application_name = data_get($application, 'name'); | ||||||
|         $this->project_uuid = data_get($application, 'environment.project.uuid'); |         $this->project_uuid = data_get($application, 'environment.project.uuid'); | ||||||
|  |         $this->environment_uuid = data_get($application, 'environment.uuid'); | ||||||
|         $this->environment_name = data_get($application, 'environment.name'); |         $this->environment_name = data_get($application, 'environment.name'); | ||||||
|         $this->fqdn = data_get($application, 'fqdn'); |         $this->fqdn = data_get($application, 'fqdn'); | ||||||
|         if (str($this->fqdn)->explode(',')->count() > 1) { |         if (str($this->fqdn)->explode(',')->count() > 1) { | ||||||
|             $this->fqdn = str($this->fqdn)->explode(',')->first(); |             $this->fqdn = str($this->fqdn)->explode(',')->first(); | ||||||
|         } |         } | ||||||
|         $this->deployment_url = base_url()."/project/{$this->project_uuid}/".urlencode($this->environment_name)."/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; |         $this->deployment_url = base_url()."/project/{$this->project_uuid}/environments/{$this->environment_uuid}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
|   | |||||||
| @@ -22,6 +22,8 @@ class DeploymentSuccess extends CustomEmailNotification | |||||||
| 
 | 
 | ||||||
|     public string $project_uuid; |     public string $project_uuid; | ||||||
| 
 | 
 | ||||||
|  |     public string $environment_uuid; | ||||||
|  | 
 | ||||||
|     public string $environment_name; |     public string $environment_name; | ||||||
| 
 | 
 | ||||||
|     public ?string $deployment_url = null; |     public ?string $deployment_url = null; | ||||||
| @@ -36,12 +38,13 @@ class DeploymentSuccess extends CustomEmailNotification | |||||||
|         $this->preview = $preview; |         $this->preview = $preview; | ||||||
|         $this->application_name = data_get($application, 'name'); |         $this->application_name = data_get($application, 'name'); | ||||||
|         $this->project_uuid = data_get($application, 'environment.project.uuid'); |         $this->project_uuid = data_get($application, 'environment.project.uuid'); | ||||||
|  |         $this->environment_uuid = data_get($application, 'environment.uuid'); | ||||||
|         $this->environment_name = data_get($application, 'environment.name'); |         $this->environment_name = data_get($application, 'environment.name'); | ||||||
|         $this->fqdn = data_get($application, 'fqdn'); |         $this->fqdn = data_get($application, 'fqdn'); | ||||||
|         if (str($this->fqdn)->explode(',')->count() > 1) { |         if (str($this->fqdn)->explode(',')->count() > 1) { | ||||||
|             $this->fqdn = str($this->fqdn)->explode(',')->first(); |             $this->fqdn = str($this->fqdn)->explode(',')->first(); | ||||||
|         } |         } | ||||||
|         $this->deployment_url = base_url()."/project/{$this->project_uuid}/".urlencode($this->environment_name)."/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; |         $this->deployment_url = base_url()."/project/{$this->project_uuid}/environments/{$this->environment_uuid}/application/{$this->application->uuid}/deployment/{$this->deployment_uuid}"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
| @@ -144,7 +147,7 @@ class DeploymentSuccess extends CustomEmailNotification | |||||||
|     { |     { | ||||||
|         if ($this->preview) { |         if ($this->preview) { | ||||||
|             $title = "Pull request #{$this->preview->pull_request_id} successfully deployed"; |             $title = "Pull request #{$this->preview->pull_request_id} successfully deployed"; | ||||||
|             $message = 'New PR' . $this->preview->pull_request_id . ' version successfully deployed of ' . $this->application_name . ''; |             $message = 'New PR'.$this->preview->pull_request_id.' version successfully deployed of '.$this->application_name.''; | ||||||
|             if ($this->preview->fqdn) { |             if ($this->preview->fqdn) { | ||||||
|                 $buttons[] = [ |                 $buttons[] = [ | ||||||
|                     'text' => 'Open Application', |                     'text' => 'Open Application', | ||||||
| @@ -153,7 +156,7 @@ class DeploymentSuccess extends CustomEmailNotification | |||||||
|             } |             } | ||||||
|         } else { |         } else { | ||||||
|             $title = 'New version successfully deployed'; |             $title = 'New version successfully deployed'; | ||||||
|             $message = 'New version successfully deployed of ' . $this->application_name . ''; |             $message = 'New version successfully deployed of '.$this->application_name.''; | ||||||
|             if ($this->fqdn) { |             if ($this->fqdn) { | ||||||
|                 $buttons[] = [ |                 $buttons[] = [ | ||||||
|                     'text' => 'Open Application', |                     'text' => 'Open Application', | ||||||
|   | |||||||
| @@ -15,6 +15,8 @@ class StatusChanged extends CustomEmailNotification | |||||||
| 
 | 
 | ||||||
|     public string $project_uuid; |     public string $project_uuid; | ||||||
| 
 | 
 | ||||||
|  |     public string $environment_uuid; | ||||||
|  | 
 | ||||||
|     public string $environment_name; |     public string $environment_name; | ||||||
| 
 | 
 | ||||||
|     public ?string $resource_url = null; |     public ?string $resource_url = null; | ||||||
| @@ -26,12 +28,13 @@ class StatusChanged extends CustomEmailNotification | |||||||
|         $this->onQueue('high'); |         $this->onQueue('high'); | ||||||
|         $this->resource_name = data_get($resource, 'name'); |         $this->resource_name = data_get($resource, 'name'); | ||||||
|         $this->project_uuid = data_get($resource, 'environment.project.uuid'); |         $this->project_uuid = data_get($resource, 'environment.project.uuid'); | ||||||
|  |         $this->environment_uuid = data_get($resource, 'environment.uuid'); | ||||||
|         $this->environment_name = data_get($resource, 'environment.name'); |         $this->environment_name = data_get($resource, 'environment.name'); | ||||||
|         $this->fqdn = data_get($resource, 'fqdn', null); |         $this->fqdn = data_get($resource, 'fqdn', null); | ||||||
|         if (str($this->fqdn)->explode(',')->count() > 1) { |         if (str($this->fqdn)->explode(',')->count() > 1) { | ||||||
|             $this->fqdn = str($this->fqdn)->explode(',')->first(); |             $this->fqdn = str($this->fqdn)->explode(',')->first(); | ||||||
|         } |         } | ||||||
|         $this->resource_url = base_url()."/project/{$this->project_uuid}/".urlencode($this->environment_name)."/application/{$this->resource->uuid}"; |         $this->resource_url = base_url()."/project/{$this->project_uuid}/environments/{$this->environment_uuid}/application/{$this->resource->uuid}"; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     public function via(object $notifiable): array |     public function via(object $notifiable): array | ||||||
| @@ -80,7 +83,7 @@ class StatusChanged extends CustomEmailNotification | |||||||
| 
 | 
 | ||||||
|     public function toPushover(): PushoverMessage |     public function toPushover(): PushoverMessage | ||||||
|     { |     { | ||||||
|         $message = $this->resource_name . ' has been stopped.'; |         $message = $this->resource_name.' has been stopped.'; | ||||||
| 
 | 
 | ||||||
|         return new PushoverMessage( |         return new PushoverMessage( | ||||||
|             title: 'Application stopped', |             title: 'Application stopped', | ||||||
|   | |||||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	 basigabri
					basigabri