Merge branch 'next' into auto-cleanup-improvements

This commit is contained in:
Andras Bacsai
2024-08-26 11:13:40 +02:00
committed by GitHub
116 changed files with 3666 additions and 1157 deletions

View File

@@ -49,6 +49,8 @@ Special thanks to our biggest sponsors!
<a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a> <a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a>
<a href="https://latitude.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/latitude.svg" alt="latitude logo" width="200"/></a> <a href="https://latitude.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/latitude.svg" alt="latitude logo" width="200"/></a>
<a href="https://brand.dev/?ref=coolify.io" target="_blank"><img src="./other/logos/branddev.png" alt="branddev logo" width="200"/></a> <a href="https://brand.dev/?ref=coolify.io" target="_blank"><img src="./other/logos/branddev.png" alt="branddev logo" width="200"/></a>
<a href="https://jobscollider.com/remote-jobs?ref=coolify.io" target="_blank"><img src="./other/logos/jobscollider.svg" alt="jobscollider logo" width="200"/></a>
<a href="https://hostinger.com?ref=coolify.io" target="_blank"><img src="./other/logos/hostinger.svg" alt="hostinger logo" width="200"/></a>
## Github Sponsors ($40+) ## Github Sponsors ($40+)
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a> <a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
@@ -71,8 +73,10 @@ Special thanks to our biggest sponsors!
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a> <a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
<a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a> <a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a>
<a href="https://il.ly"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a> <a href="https://il.ly"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
<a href="https://www.breakcold.com/?utm_source=coolify.io"><img src="https://github.com/breakcold.png" width="60px" alt="Breakcold" /></a>
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a> <a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a> <a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
<a href="https://formbricks.com/?utm_source=coolify.io"><img src="https://github.com/formbricks.png" width="60px" alt="Formbricks" /></a>
## Organizations ## Organizations
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a> <a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>

View File

@@ -21,7 +21,7 @@ class StartClickhouse
$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;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@@ -75,7 +75,7 @@ class StartClickhouse
], ],
], ],
]; ];
if (! is_null($this->database->limits_cpuset)) { if (!is_null($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()) {
@@ -102,6 +102,11 @@ class StartClickhouse
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
@@ -120,10 +125,10 @@ class StartClickhouse
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
} else { } else {
$volume_name = $persistentStorage->name; $volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
} }
} }
@@ -154,11 +159,11 @@ class StartClickhouse
$environment_variables->push("$env->key=$env->real_value"); $environment_variables->push("$env->key=$env->real_value");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_USER'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('CLICKHOUSE_ADMIN_USER'))->isEmpty()) {
$environment_variables->push("CLICKHOUSE_ADMIN_USER={$this->database->clickhouse_admin_user}"); $environment_variables->push("CLICKHOUSE_ADMIN_USER={$this->database->clickhouse_admin_user}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('CLICKHOUSE_ADMIN_PASSWORD'))->isEmpty()) {
$environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}"); $environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}");
} }

View File

@@ -23,7 +23,7 @@ class StartDragonfly
$startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}"; $startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}";
$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;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@@ -75,7 +75,7 @@ class StartDragonfly
], ],
], ],
]; ];
if (! is_null($this->database->limits_cpuset)) { if (!is_null($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()) {
@@ -102,6 +102,11 @@ class StartDragonfly
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
@@ -120,10 +125,10 @@ class StartDragonfly
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
} else { } else {
$volume_name = $persistentStorage->name; $volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
} }
} }
@@ -154,7 +159,7 @@ class StartDragonfly
$environment_variables->push("$env->key=$env->real_value"); $environment_variables->push("$env->key=$env->real_value");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
$environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}"); $environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}");
} }

View File

@@ -24,7 +24,7 @@ class StartKeydb
$startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes"; $startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes";
$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;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@@ -74,7 +74,7 @@ class StartKeydb
], ],
], ],
]; ];
if (! is_null($this->database->limits_cpuset)) { if (!is_null($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()) {
@@ -101,15 +101,19 @@ class StartKeydb
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
if (! is_null($this->database->keydb_conf) || ! empty($this->database->keydb_conf)) { if (!is_null($this->database->keydb_conf) || !empty($this->database->keydb_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir.'/keydb.conf', 'source' => $this->configuration_dir . '/keydb.conf',
'target' => '/etc/keydb/keydb.conf', 'target' => '/etc/keydb/keydb.conf',
'read_only' => true, 'read_only' => true,
]; ];
$docker_compose['services'][$container_name]['command'] = "keydb-server /etc/keydb/keydb.conf --requirepass {$this->database->keydb_password} --appendonly yes"; $docker_compose['services'][$container_name]['command'] = "keydb-server /etc/keydb/keydb.conf --requirepass {$this->database->keydb_password} --appendonly yes";
} }
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
@@ -128,10 +132,10 @@ class StartKeydb
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
} else { } else {
$volume_name = $persistentStorage->name; $volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
} }
} }
@@ -162,7 +166,7 @@ class StartKeydb
$environment_variables->push("$env->key=$env->real_value"); $environment_variables->push("$env->key=$env->real_value");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}"); $environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
} }

View File

@@ -21,7 +21,7 @@ class StartMariadb
$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;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@@ -69,7 +69,7 @@ class StartMariadb
], ],
], ],
]; ];
if (! is_null($this->database->limits_cpuset)) { if (!is_null($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()) {
@@ -96,14 +96,19 @@ class StartMariadb
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
if (! is_null($this->database->mariadb_conf) || ! empty($this->database->mariadb_conf)) { if (!is_null($this->database->mariadb_conf) || !empty($this->database->mariadb_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir.'/custom-config.cnf', 'source' => $this->configuration_dir . '/custom-config.cnf',
'target' => '/etc/mysql/conf.d/custom-config.cnf', 'target' => '/etc/mysql/conf.d/custom-config.cnf',
'read_only' => true, 'read_only' => true,
]; ];
} }
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
@@ -122,10 +127,10 @@ class StartMariadb
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
} else { } else {
$volume_name = $persistentStorage->name; $volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
} }
} }
@@ -156,18 +161,18 @@ class StartMariadb
$environment_variables->push("$env->key=$env->real_value"); $environment_variables->push("$env->key=$env->real_value");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}"); $environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}"); $environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) {
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}"); $environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}"); $environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
} }

View File

@@ -23,7 +23,7 @@ class StartMongodb
$startCommand = 'mongod'; $startCommand = 'mongod';
$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;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@@ -77,7 +77,7 @@ class StartMongodb
], ],
], ],
]; ];
if (! is_null($this->database->limits_cpuset)) { if (!is_null($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()) {
@@ -104,23 +104,27 @@ class StartMongodb
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
if (! is_null($this->database->mongo_conf) || ! empty($this->database->mongo_conf)) { if (!is_null($this->database->mongo_conf) || !empty($this->database->mongo_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir.'/mongod.conf', 'source' => $this->configuration_dir . '/mongod.conf',
'target' => '/etc/mongo/mongod.conf', 'target' => '/etc/mongo/mongod.conf',
'read_only' => true, 'read_only' => true,
]; ];
$docker_compose['services'][$container_name]['command'] = $startCommand.' --config /etc/mongo/mongod.conf'; $docker_compose['services'][$container_name]['command'] = $startCommand . ' --config /etc/mongo/mongod.conf';
} }
$this->add_default_database(); $this->add_default_database();
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir.'/docker-entrypoint-initdb.d', 'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d',
'target' => '/docker-entrypoint-initdb.d', 'target' => '/docker-entrypoint-initdb.d',
'read_only' => true, 'read_only' => true,
]; ];
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
@@ -139,10 +143,10 @@ class StartMongodb
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
} else { } else {
$volume_name = $persistentStorage->name; $volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
} }
} }
@@ -173,15 +177,15 @@ class StartMongodb
$environment_variables->push("$env->key=$env->real_value"); $environment_variables->push("$env->key=$env->real_value");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}"); $environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}"); $environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}"); $environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
} }

View File

@@ -21,7 +21,7 @@ class StartMysql
$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;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@@ -69,7 +69,7 @@ class StartMysql
], ],
], ],
]; ];
if (! is_null($this->database->limits_cpuset)) { if (!is_null($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()) {
@@ -96,14 +96,19 @@ class StartMysql
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
if (! is_null($this->database->mysql_conf) || ! empty($this->database->mysql_conf)) { if (!is_null($this->database->mysql_conf) || !empty($this->database->mysql_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir.'/custom-config.cnf', 'source' => $this->configuration_dir . '/custom-config.cnf',
'target' => '/etc/mysql/conf.d/custom-config.cnf', 'target' => '/etc/mysql/conf.d/custom-config.cnf',
'read_only' => true, 'read_only' => true,
]; ];
} }
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
@@ -122,10 +127,10 @@ class StartMysql
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
} else { } else {
$volume_name = $persistentStorage->name; $volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
} }
} }
@@ -156,18 +161,18 @@ class StartMysql
$environment_variables->push("$env->key=$env->real_value"); $environment_variables->push("$env->key=$env->real_value");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}"); $environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}"); $environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) {
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}"); $environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}"); $environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
} }

View File

@@ -37,6 +37,7 @@ class StartPostgresql
$this->generate_init_scripts(); $this->generate_init_scripts();
$this->add_custom_conf(); $this->add_custom_conf();
$docker_compose = [ $docker_compose = [
'services' => [ 'services' => [
$container_name => [ $container_name => [
@@ -126,6 +127,10 @@ class StartPostgresql
'config_file=/etc/postgresql/postgresql.conf', 'config_file=/etc/postgresql/postgresql.conf',
]; ];
} }
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";

View File

@@ -24,7 +24,7 @@ class StartRedis
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes"; $startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
$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;
$this->commands = [ $this->commands = [
"echo 'Starting {$database->name}.'", "echo 'Starting {$database->name}.'",
@@ -78,7 +78,7 @@ class StartRedis
], ],
], ],
]; ];
if (! is_null($this->database->limits_cpuset)) { if (!is_null($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()) {
@@ -105,15 +105,20 @@ class StartRedis
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
} }
if (! is_null($this->database->redis_conf) || ! empty($this->database->redis_conf)) { if (!is_null($this->database->redis_conf) || !empty($this->database->redis_conf)) {
$docker_compose['services'][$container_name]['volumes'][] = [ $docker_compose['services'][$container_name]['volumes'][] = [
'type' => 'bind', 'type' => 'bind',
'source' => $this->configuration_dir.'/redis.conf', 'source' => $this->configuration_dir . '/redis.conf',
'target' => '/usr/local/etc/redis/redis.conf', 'target' => '/usr/local/etc/redis/redis.conf',
'read_only' => true, 'read_only' => true,
]; ];
$docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes"; $docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
} }
// Add custom docker run options
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
$docker_compose = Yaml::dump($docker_compose, 10); $docker_compose = Yaml::dump($docker_compose, 10);
$docker_compose_base64 = base64_encode($docker_compose); $docker_compose_base64 = base64_encode($docker_compose);
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null"; $this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
@@ -132,10 +137,10 @@ class StartRedis
$local_persistent_volumes = []; $local_persistent_volumes = [];
foreach ($this->database->persistentStorages as $persistentStorage) { foreach ($this->database->persistentStorages as $persistentStorage) {
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) { if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
} else { } else {
$volume_name = $persistentStorage->name; $volume_name = $persistentStorage->name;
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path; $local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
} }
} }
@@ -166,7 +171,7 @@ class StartRedis
$environment_variables->push("$env->key=$env->real_value"); $environment_variables->push("$env->key=$env->real_value");
} }
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) { if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}"); $environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
} }

View File

@@ -9,8 +9,9 @@ class CleanupDocker
{ {
use AsAction; use AsAction;
public function handle(Server $server, bool $force = true) public function handle(Server $server)
{ {
$commands = $this->getCommands($force); $commands = $this->getCommands($force);
foreach ($commands as $command) { foreach ($commands as $command) {
@@ -28,7 +29,7 @@ class CleanupDocker
if ($force) { if ($force) {
return array_merge([ return array_merge([
'docker container rm $(docker container ls -aq --filter status=exited --filter "label=coolify.managed=true")', 'docker container prune -f --filter "label=coolify.managed=true"',
'docker image prune -af', 'docker image prune -af',
'docker builder prune -af', 'docker builder prune -af',
], $commonCommands); ], $commonCommands);

View File

@@ -26,7 +26,7 @@ class UpdateCoolify
if (! $this->server) { if (! $this->server) {
return; return;
} }
CleanupDocker::dispatch($this->server, false)->onQueue('high'); CleanupDocker::dispatch($this->server)->onQueue('high');
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json'); $response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
if ($response->successful()) { if ($response->successful()) {
$versions = $response->json(); $versions = $response->json();

View File

@@ -132,6 +132,9 @@ class Init extends Command
private function cleanup_unused_network_from_coolify_proxy() private function cleanup_unused_network_from_coolify_proxy()
{ {
if (isCloud()) {
return;
}
foreach ($this->servers as $server) { foreach ($this->servers as $server) {
if (! $server->isFunctional()) { if (! $server->isFunctional()) {
continue; continue;

View File

@@ -0,0 +1,26 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Process;
class OpenApi extends Command
{
protected $signature = 'openapi';
protected $description = 'Generate OpenApi file.';
public function handle()
{
// Generate OpenAPI documentation
echo "Generating OpenAPI documentation.\n";
$process = Process::run(['/var/www/html/vendor/bin/openapi', 'app', '-o', 'openapi.yaml']);
$error = $process->errorOutput();
$error = preg_replace('/^.*an object literal,.*$/m', '', $error);
$error = preg_replace('/^\h*\v+/m', '', $error);
echo $error;
echo $process->output();
}
}

View File

@@ -30,6 +30,7 @@ class Kernel extends ConsoleKernel
$this->all_servers = Server::all(); $this->all_servers = Server::all();
$settings = InstanceSettings::get(); $settings = InstanceSettings::get();
$schedule->command('telescope:prune')->daily();
if (isDev()) { if (isDev()) {
// Instance Jobs // Instance Jobs
$schedule->command('horizon:snapshot')->everyMinute(); $schedule->command('horizon:snapshot')->everyMinute();

View File

@@ -201,7 +201,7 @@ class ApplicationsController extends Controller
#[OA\Post( #[OA\Post(
summary: 'Create (Private - GH App)', summary: 'Create (Private - GH App)',
description: 'Create new application based on a private repository through a Github App.', description: 'Create new application based on a private repository through a Github App.',
path: '/applications/private-gh-app', path: '/applications/private-github-app',
security: [ security: [
['bearerAuth' => []], ['bearerAuth' => []],
], ],
@@ -1450,7 +1450,7 @@ class ApplicationsController extends Controller
], 404); ], 404);
} }
$server = $application->destination->server; $server = $application->destination->server;
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect']; $allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy'];
$validator = customApiValidator($request->all(), [ $validator = customApiValidator($request->all(), [
sharedDataApplications(), sharedDataApplications(),
@@ -1526,6 +1526,10 @@ class ApplicationsController extends Controller
} }
$request->offsetUnset('docker_compose_domains'); $request->offsetUnset('docker_compose_domains');
} }
$instantDeploy = $request->instant_deploy;
removeUnnecessaryFieldsFromRequest($request);
$data = $request->all(); $data = $request->all();
data_set($data, 'fqdn', $domains); data_set($data, 'fqdn', $domains);
if ($dockerComposeDomainsJson->count() > 0) { if ($dockerComposeDomainsJson->count() > 0) {
@@ -1534,6 +1538,16 @@ class ApplicationsController extends Controller
$application->fill($data); $application->fill($data);
$application->save(); $application->save();
if ($instantDeploy) {
$deployment_uuid = new Cuid2;
queue_application_deployment(
application: $application,
deployment_uuid: $deployment_uuid,
is_api: true,
);
}
return response()->json([ return response()->json([
'uuid' => $application->uuid, 'uuid' => $application->uuid,
]); ]);

View File

@@ -1,35 +0,0 @@
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\EnvironmentVariable;
use Illuminate\Http\Request;
class EnvironmentVariablesController extends Controller
{
public function delete_env_by_uuid(Request $request)
{
$teamId = getTeamIdFromToken();
if (is_null($teamId)) {
return invalidTokenResponse();
}
$env = EnvironmentVariable::where('uuid', $request->env_uuid)->first();
if (! $env) {
return response()->json([
'message' => 'Environment variable not found.',
], 404);
}
$found_app = $env->resource()->whereRelation('environment.project.team', 'id', $teamId)->first();
if (! $found_app) {
return response()->json([
'message' => 'Environment variable not found.',
], 404);
}
$env->delete();
return response()->json([
'message' => 'Environment variable deleted.',
]);
}
}

View File

@@ -157,7 +157,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
private ?string $coolify_variables = null; private ?string $coolify_variables = null;
private bool $preserveRepository = true; private bool $preserveRepository = false;
public $tries = 1; public $tries = 1;
@@ -198,9 +198,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id); $this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
if ($this->application->settings->custom_internal_name && ! $this->application->settings->is_consistent_container_name_enabled) { if ($this->application->settings->custom_internal_name && ! $this->application->settings->is_consistent_container_name_enabled) {
if ($this->pull_request_id === 0) {
$this->container_name = $this->application->settings->custom_internal_name; $this->container_name = $this->application->settings->custom_internal_name;
} else {
$this->container_name = "{$this->application->settings->custom_internal_name}-pr-{$this->pull_request_id}";
} }
ray('New container name: ', $this->container_name); }
ray('New container name: ', $this->container_name)->green();
savePrivateKeyToFs($this->server); savePrivateKeyToFs($this->server);
$this->saved_outputs = collect(); $this->saved_outputs = collect();
@@ -276,6 +280,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->original_server = $this->server; $this->original_server = $this->server;
} else { } else {
$this->build_server = $buildServers->random(); $this->build_server = $buildServers->random();
$this->application_deployment_queue->build_server_id = $this->build_server->id;
$this->application_deployment_queue->addLogEntry("Found a suitable build server ({$this->build_server->name})."); $this->application_deployment_queue->addLogEntry("Found a suitable build server ({$this->build_server->name}).");
$this->original_server = $this->server; $this->original_server = $this->server;
$this->use_build_server = true; $this->use_build_server = true;
@@ -414,15 +419,43 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->prepare_builder_image(); $this->prepare_builder_image();
$this->check_git_if_build_needed(); $this->check_git_if_build_needed();
$this->clone_repository(); $this->clone_repository();
if ($this->preserveRepository) {
foreach ($this->application->fileStorages as $fileStorage) {
$path = $fileStorage->fs_path;
$saveName = 'file_stat_'.$fileStorage->id;
$realPathInGit = str($path)->replace($this->application->workdir(), $this->workdir)->value();
// check if the file is a directory or a file inside the repository
$this->execute_remote_command(
[executeInDocker($this->deployment_uuid, "stat -c '%F' {$realPathInGit}"), 'hidden' => true, 'ignore_errors' => true, 'save' => $saveName]
);
if ($this->saved_outputs->has($saveName)) {
$fileStat = $this->saved_outputs->get($saveName);
if ($fileStat->value() === 'directory' && ! $fileStorage->is_directory) {
$fileStorage->is_directory = true;
$fileStorage->content = null;
$fileStorage->save();
$fileStorage->deleteStorageOnServer();
$fileStorage->saveStorageOnServer();
} elseif ($fileStat->value() === 'regular file' && $fileStorage->is_directory) {
$fileStorage->is_directory = false;
$fileStorage->is_based_on_git = true;
$fileStorage->save();
$fileStorage->deleteStorageOnServer();
$fileStorage->saveStorageOnServer();
}
}
}
}
$this->generate_image_names(); $this->generate_image_names();
$this->cleanup_git(); $this->cleanup_git();
$this->application->loadComposeFile(isInit: false); $this->application->loadComposeFile(isInit: false);
if ($this->application->settings->is_raw_compose_deployment_enabled) { if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose(); $this->application->oldRawParser();
$yaml = $composeFile = $this->application->docker_compose_raw; $yaml = $composeFile = $this->application->docker_compose_raw;
$this->save_environment_variables(); $this->save_environment_variables();
} else { } else {
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id, preview_id: data_get($this, 'preview.id')); $composeFile = $this->application->oldParser(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id'));
$this->save_environment_variables(); $this->save_environment_variables();
if (! is_null($this->env_filename)) { if (! is_null($this->env_filename)) {
$services = collect($composeFile['services']); $services = collect($composeFile['services']);
@@ -439,7 +472,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
return; return;
} }
$yaml = Yaml::dump($composeFile->toArray(), 10); $yaml = Yaml::dump(convertToArray($composeFile), 10);
} }
$this->docker_compose_base64 = base64_encode($yaml); $this->docker_compose_base64 = base64_encode($yaml);
$this->execute_remote_command([ $this->execute_remote_command([
@@ -480,6 +513,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
} }
// Start compose file // Start compose file
$server_workdir = $this->application->workdir();
if ($this->application->settings->is_raw_compose_deployment_enabled) { if ($this->application->settings->is_raw_compose_deployment_enabled) {
if ($this->docker_compose_custom_start_command) { if ($this->docker_compose_custom_start_command) {
$this->write_deployment_configurations(); $this->write_deployment_configurations();
@@ -488,7 +522,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
); );
} else { } else {
$this->write_deployment_configurations(); $this->write_deployment_configurations();
$server_workdir = $this->application->workdir();
$this->docker_compose_location = '/docker-compose.yaml'; $this->docker_compose_location = '/docker-compose.yaml';
$command = "{$this->coolify_variables} docker compose"; $command = "{$this->coolify_variables} docker compose";
@@ -508,15 +541,27 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
); );
} else { } else {
$command = "{$this->coolify_variables} docker compose"; $command = "{$this->coolify_variables} docker compose";
if ($this->preserveRepository) {
if ($this->env_filename) {
$command .= " --env-file {$server_workdir}/{$this->env_filename}";
}
$command .= " --project-name {$this->application->uuid} --project-directory {$server_workdir} -f {$server_workdir}{$this->docker_compose_location} up -d";
$this->write_deployment_configurations();
$this->execute_remote_command(
['command' => $command, 'hidden' => true],
);
} else {
if ($this->env_filename) { if ($this->env_filename) {
$command .= " --env-file {$this->workdir}/{$this->env_filename}"; $command .= " --env-file {$this->workdir}/{$this->env_filename}";
} }
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d"; $command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
$this->write_deployment_configurations();
$this->execute_remote_command( $this->execute_remote_command(
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true], [executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
); );
$this->write_deployment_configurations();
}
} }
} }
@@ -610,15 +655,16 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
[ [
"mkdir -p $this->configuration_dir", "mkdir -p $this->configuration_dir",
], ],
// removing this now as we are using docker cp
// [
// "rm -rf $this->configuration_dir/{*,.*}",
// ],
[ [
"docker cp {$this->deployment_uuid}:{$this->workdir}/. {$this->configuration_dir}", "docker cp {$this->deployment_uuid}:{$this->workdir}/. {$this->configuration_dir}",
], ],
); );
} }
foreach ($this->application->fileStorages as $fileStorage) {
if (! $fileStorage->is_based_on_git && ! $fileStorage->is_directory) {
$fileStorage->saveStorageOnServer();
}
}
if ($this->use_build_server) { if ($this->use_build_server) {
$this->server = $this->build_server; $this->server = $this->build_server;
} }
@@ -851,12 +897,14 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', ''); $url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', '');
$envs->push("COOLIFY_URL={$url}"); $envs->push("COOLIFY_URL={$url}");
} }
if ($this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) { if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$local_branch}"); $envs->push("COOLIFY_BRANCH={$local_branch}");
} }
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}"); $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
} }
}
foreach ($sorted_environment_variables_preview as $env) { foreach ($sorted_environment_variables_preview as $env) {
$real_value = $env->real_value; $real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') { if ($env->version === '4.0.0-beta.239') {
@@ -897,12 +945,14 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', ''); $url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
$envs->push("COOLIFY_URL={$url}"); $envs->push("COOLIFY_URL={$url}");
} }
if ($this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) { if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$envs->push("COOLIFY_BRANCH={$local_branch}"); $envs->push("COOLIFY_BRANCH={$local_branch}");
} }
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) { if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}"); $envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
} }
}
foreach ($sorted_environment_variables as $env) { foreach ($sorted_environment_variables as $env) {
$real_value = $env->real_value; $real_value = $env->real_value;
if ($env->version === '4.0.0-beta.239') { if ($env->version === '4.0.0-beta.239') {
@@ -1708,13 +1758,20 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) { if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
$docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array; $docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array;
} }
if (count($persistent_storages) > 0) { if (count($persistent_storages) > 0) {
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_storages; if (! data_get($docker_compose, 'services.'.$this->container_name.'.volumes')) {
$docker_compose['services'][$this->container_name]['volumes'] = [];
}
$docker_compose['services'][$this->container_name]['volumes'] = array_merge($docker_compose['services'][$this->container_name]['volumes'], $persistent_storages);
} }
if (count($persistent_file_volumes) > 0) { if (count($persistent_file_volumes) > 0) {
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_file_volumes->map(function ($item) { if (! data_get($docker_compose, 'services.'.$this->container_name.'.volumes')) {
$docker_compose['services'][$this->container_name]['volumes'] = [];
}
$docker_compose['services'][$this->container_name]['volumes'] = array_merge($docker_compose['services'][$this->container_name]['volumes'], $persistent_file_volumes->map(function ($item) {
return "$item->fs_path:$item->mount_path"; return "$item->fs_path:$item->mount_path";
})->toArray(); })->toArray());
} }
if (count($volume_names) > 0) { if (count($volume_names) > 0) {
$docker_compose['volumes'] = $volume_names; $docker_compose['volumes'] = $volume_names;
@@ -2140,7 +2197,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
} else { } else {
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}"); $dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
} }
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
} }
} }
$dockerfile_base64 = base64_encode($dockerfile->implode("\n")); $dockerfile_base64 = base64_encode($dockerfile->implode("\n"));

View File

@@ -56,6 +56,8 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
public ?string $backup_output = null; public ?string $backup_output = null;
public ?string $postgres_password = null;
public ?S3Storage $s3 = null; public ?S3Storage $s3 = null;
public function __construct($backup) public function __construct($backup)
@@ -89,8 +91,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
public function handle(): void public function handle(): void
{ {
try { try {
BackupCreated::dispatch($this->team->id);
// Check if team is exists // Check if team is exists
if (is_null($this->team)) { if (is_null($this->team)) {
$this->backup->update(['status' => 'failed']); $this->backup->update(['status' => 'failed']);
@@ -99,6 +99,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
return; return;
} }
BackupCreated::dispatch($this->team->id);
$status = str(data_get($this->database, 'status')); $status = str(data_get($this->database, 'status'));
if (! $status->startsWith('running') && $this->database->id !== 0) { if (! $status->startsWith('running') && $this->database->id !== 0) {
ray('database not running'); ray('database not running');
@@ -134,6 +137,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
} else { } else {
$databasesToBackup = $this->database->postgres_user; $databasesToBackup = $this->database->postgres_user;
} }
$this->postgres_password = $envs->filter(function ($env) {
return str($env)->startsWith('POSTGRES_PASSWORD=');
})->first();
if ($this->postgres_password) {
$this->postgres_password = str($this->postgres_password)->after('POSTGRES_PASSWORD=')->value();
}
} elseif (str($databaseType)->contains('mysql')) { } elseif (str($databaseType)->contains('mysql')) {
$this->container_name = "{$this->database->name}-$serviceUuid"; $this->container_name = "{$this->database->name}-$serviceUuid";
$this->directory_name = $serviceName.'-'.$this->container_name; $this->directory_name = $serviceName.'-'.$this->container_name;
@@ -336,7 +346,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
$url = $this->database->internal_db_url; $url = $this->database->internal_db_url;
if ($databaseWithCollections === 'all') { if ($databaseWithCollections === 'all') {
$commands[] = 'mkdir -p '.$this->backup_dir; $commands[] = 'mkdir -p '.$this->backup_dir;
if (str($this->database->image)->startsWith('mongo:4.0')) { if (str($this->database->image)->startsWith('mongo:4')) {
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
} else { } else {
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
@@ -351,13 +361,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
} }
$commands[] = 'mkdir -p '.$this->backup_dir; $commands[] = 'mkdir -p '.$this->backup_dir;
if ($collectionsToExclude->count() === 0) { if ($collectionsToExclude->count() === 0) {
if (str($this->database->image)->startsWith('mongo:4.0')) { if (str($this->database->image)->startsWith('mongo:4')) {
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
} else { } else {
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location";
} }
} else { } else {
if (str($this->database->image)->startsWith('mongo:4.0')) { if (str($this->database->image)->startsWith('mongo:4')) {
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
} else { } else {
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location"; $commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
@@ -381,7 +391,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
{ {
try { try {
$commands[] = 'mkdir -p '.$this->backup_dir; $commands[] = 'mkdir -p '.$this->backup_dir;
$commands[] = "docker exec $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location"; $backupCommand = 'docker exec';
if ($this->postgres_password) {
$backupCommand .= " -e PGPASSWORD=$this->postgres_password";
}
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
$commands[] = $backupCommand;
$this->backup_output = instant_remote_process($commands, $this->server); $this->backup_output = instant_remote_process($commands, $this->server);
$this->backup_output = trim($this->backup_output); $this->backup_output = trim($this->backup_output);
if ($this->backup_output === '') { if ($this->backup_output === '') {
@@ -452,7 +468,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
if ($this->backup->number_of_backups_locally === 0) { if ($this->backup->number_of_backups_locally === 0) {
$deletable = $this->backup->executions()->where('status', 'success'); $deletable = $this->backup->executions()->where('status', 'success');
} else { } else {
$deletable = $this->backup->executions()->where('status', 'success')->orderByDesc('created_at')->skip($this->backup->number_of_backups_locally - 1); $deletable = $this->backup->executions()->where('status', 'success')->skip($this->backup->number_of_backups_locally - 1);
} }
foreach ($deletable->get() as $execution) { foreach ($deletable->get() as $execution) {
delete_backup_locally($execution->filename, $this->server); delete_backup_locally($execution->filename, $this->server);

View File

@@ -19,7 +19,9 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
public $timeout = 300; public $timeout = 300;
public int|string|null $usageBefore = null; public $tries = 2;
public ?string $usageBefore = null;
public function __construct(public Server $server) {} public function __construct(public Server $server) {}
@@ -36,6 +38,10 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
return; return;
} }
return;
}
try {
$this->usageBefore = $this->server->getDiskUsage(); $this->usageBefore = $this->server->getDiskUsage();
if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) { if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
Log::info('DockerCleanupJob force cleanup on ' . $this->server->name); Log::info('DockerCleanupJob force cleanup on ' . $this->server->name);
@@ -44,7 +50,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
return; return;
} }
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) { if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
CleanupDocker::run(server: $this->server, force: false); CleanupDocker::run(server: $this->server);
$usageAfter = $this->server->getDiskUsage(); $usageAfter = $this->server->getDiskUsage();
if ($usageAfter < $this->usageBefore) { if ($usageAfter < $this->usageBefore) {
$this->server->team?->notify(new DockerCleanup($this->server, 'Saved ' . ($this->usageBefore - $usageAfter) . '% disk space.')); $this->server->team?->notify(new DockerCleanup($this->server, 'Saved ' . ($this->usageBefore - $usageAfter) . '% disk space.'));
@@ -56,7 +62,8 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
Log::info('No need to clean up ' . $this->server->name); Log::info('No need to clean up ' . $this->server->name);
} }
} catch (\Throwable $e) { } catch (\Throwable $e) {
ray($e->getMessage()); CleanupDocker::run(server: $this->server);
Log::error('DockerCleanupJob failed: '.$e->getMessage());
throw $e; throw $e;
} }
} }

View File

@@ -66,9 +66,9 @@ class Advanced extends Component
$this->dispatch('resetDefaultLabels', false); $this->dispatch('resetDefaultLabels', false);
} }
if ($this->application->settings->is_raw_compose_deployment_enabled) { if ($this->application->settings->is_raw_compose_deployment_enabled) {
$this->application->parseRawCompose(); $this->application->oldRawParser();
} else { } else {
$this->application->parseCompose(); $this->application->oldParser();
} }
$this->application->settings->save(); $this->application->settings->save();
$this->dispatch('success', 'Settings saved.'); $this->dispatch('success', 'Settings saved.');
@@ -96,6 +96,12 @@ class Advanced extends Component
} else { } else {
$this->application->settings->custom_internal_name = null; $this->application->settings->custom_internal_name = null;
} }
if (is_null($this->application->settings->custom_internal_name)) {
$this->application->settings->save();
$this->dispatch('success', 'Custom name saved.');
return;
}
$customInternalName = $this->application->settings->custom_internal_name; $customInternalName = $this->application->settings->custom_internal_name;
$server = $this->application->destination->server; $server = $this->application->destination->server;
$allApplications = $server->applications(); $allApplications = $server->applications();

View File

@@ -55,9 +55,14 @@ class DeploymentNavbar extends Component
public function cancel() public function cancel()
{ {
$kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}"; $kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}";
$build_server_id = $this->application_deployment_queue->build_server_id;
$server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id; $server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id;
try { try {
if ($this->application->settings->is_build_server_enabled) {
$server = Server::find($build_server_id);
} else {
$server = Server::find($server_id); $server = Server::find($server_id);
}
if ($this->application_deployment_queue->logs) { if ($this->application_deployment_queue->logs) {
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR); $previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);

View File

@@ -3,8 +3,8 @@
namespace App\Livewire\Project\Application; namespace App\Livewire\Project\Application;
use App\Models\Application; use App\Models\Application;
use App\Models\LocalFileVolume;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Log;
use Livewire\Component; use Livewire\Component;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
@@ -30,6 +30,8 @@ class General extends Component
public ?string $ports_exposes = null; public ?string $ports_exposes = null;
public bool $is_preserve_repository_enabled = false;
public bool $is_container_label_escape_enabled = true; public bool $is_container_label_escape_enabled = true;
public $customLabels; public $customLabels;
@@ -130,7 +132,7 @@ class General extends Component
public function mount() public function mount()
{ {
try { try {
$this->parsedServices = $this->application->parseCompose(); $this->parsedServices = $this->application->oldParser();
if (is_null($this->parsedServices) || empty($this->parsedServices)) { if (is_null($this->parsedServices) || empty($this->parsedServices)) {
$this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.'); $this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
@@ -145,6 +147,7 @@ class General extends Component
} }
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : []; $this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
$this->ports_exposes = $this->application->ports_exposes; $this->ports_exposes = $this->application->ports_exposes;
$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) {
@@ -168,9 +171,21 @@ class General extends Component
$this->application->settings->save(); $this->application->settings->save();
$this->dispatch('success', 'Settings saved.'); $this->dispatch('success', 'Settings saved.');
$this->application->refresh(); $this->application->refresh();
// If port_exposes changed, reset default labels
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) { if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
$this->resetDefaultLabels(false); $this->resetDefaultLabels(false);
} }
if ($this->is_preserve_repository_enabled !== $this->application->settings->is_preserve_repository_enabled) {
if ($this->application->settings->is_preserve_repository_enabled === false) {
$this->application->fileStorages->each(function ($storage) {
$storage->is_based_on_git = $this->application->settings->is_preserve_repository_enabled;
$storage->save();
});
}
}
} }
public function loadComposeFile($isInit = false) public function loadComposeFile($isInit = false)
@@ -179,39 +194,18 @@ class General extends Component
if ($isInit && $this->application->docker_compose_raw) { if ($isInit && $this->application->docker_compose_raw) {
return; return;
} }
// Must reload the application to get the latest database changes
// Why? Not sure, but it works.
$this->application->refresh();
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit); ['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit);
if (is_null($this->parsedServices)) { if (is_null($this->parsedServices)) {
$this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.'); $this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
return; return;
} }
$compose = $this->application->parseCompose(); $this->application->oldParser();
$services = data_get($compose, 'services');
if ($services) {
$volumes = collect($services)->map(function ($service) {
return data_get($service, 'volumes');
})->flatten()->filter(function ($volume) {
return str($volume)->startsWith('/data/coolify');
})->unique()->values();
foreach ($volumes as $volume) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application),
],
[
'fs_path' => $source,
'mount_path' => $target,
'resource_id' => $this->application->id,
'resource_type' => get_class($this->application),
]
);
}
}
$this->dispatch('success', 'Docker compose file loaded.'); $this->dispatch('success', 'Docker compose file loaded.');
$this->dispatch('compose_loaded'); $this->dispatch('compose_loaded');
$this->dispatch('refreshStorages'); $this->dispatch('refreshStorages');

View File

@@ -58,7 +58,7 @@ class BackupExecutions extends Component
public function refreshBackupExecutions(): void public function refreshBackupExecutions(): void
{ {
if ($this->backup) { if ($this->backup) {
$this->executions = $this->backup->executions()->get()->sortBy('created_at'); $this->executions = $this->backup->executions()->get();
} }
} }
} }

View File

@@ -31,6 +31,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -42,6 +43,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Run Options',
]; ];
public function mount() public function mount()
@@ -54,7 +56,7 @@ class General extends Component
public function instantSaveAdvanced() public function instantSaveAdvanced()
{ {
try { try {
if (! $this->server->isLogDrainEnabled()) { if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false; $this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
@@ -71,14 +73,14 @@ class General extends Component
public function instantSave() public function instantSave()
{ {
try { try {
if ($this->database->is_public && ! $this->database->public_port) { if ($this->database->is_public && !$this->database->public_port) {
$this->dispatch('error', 'Public port is required.'); $this->dispatch('error', 'Public port is required.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
if ($this->database->is_public) { if ($this->database->is_public) {
if (! str($this->database->status)->startsWith('running')) { if (!str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false; $this->database->is_public = false;
@@ -93,7 +95,7 @@ class General extends Component
$this->db_url_public = $this->database->external_db_url; $this->db_url_public = $this->database->external_db_url;
$this->database->save(); $this->database->save();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public; $this->database->is_public = !$this->database->is_public;
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -30,6 +30,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -40,6 +41,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Run Options',
]; ];
public function mount() public function mount()
@@ -52,7 +54,7 @@ class General extends Component
public function instantSaveAdvanced() public function instantSaveAdvanced()
{ {
try { try {
if (! $this->server->isLogDrainEnabled()) { if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false; $this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
@@ -86,14 +88,14 @@ class General extends Component
public function instantSave() public function instantSave()
{ {
try { try {
if ($this->database->is_public && ! $this->database->public_port) { if ($this->database->is_public && !$this->database->public_port) {
$this->dispatch('error', 'Public port is required.'); $this->dispatch('error', 'Public port is required.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
if ($this->database->is_public) { if ($this->database->is_public) {
if (! str($this->database->status)->startsWith('running')) { if (!str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false; $this->database->is_public = false;
@@ -108,7 +110,7 @@ class General extends Component
$this->db_url_public = $this->database->external_db_url; $this->db_url_public = $this->database->external_db_url;
$this->database->save(); $this->database->save();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public; $this->database->is_public = !$this->database->is_public;
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -31,6 +31,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -42,6 +43,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Run Options',
]; ];
public function mount() public function mount()
@@ -55,7 +57,7 @@ class General extends Component
public function instantSaveAdvanced() public function instantSaveAdvanced()
{ {
try { try {
if (! $this->server->isLogDrainEnabled()) { if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false; $this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
@@ -92,14 +94,14 @@ class General extends Component
public function instantSave() public function instantSave()
{ {
try { try {
if ($this->database->is_public && ! $this->database->public_port) { if ($this->database->is_public && !$this->database->public_port) {
$this->dispatch('error', 'Public port is required.'); $this->dispatch('error', 'Public port is required.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
if ($this->database->is_public) { if ($this->database->is_public) {
if (! str($this->database->status)->startsWith('running')) { if (!str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false; $this->database->is_public = false;
@@ -114,7 +116,7 @@ class General extends Component
$this->db_url_public = $this->database->external_db_url; $this->db_url_public = $this->database->external_db_url;
$this->database->save(); $this->database->save();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public; $this->database->is_public = !$this->database->is_public;
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -34,6 +34,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -48,6 +49,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Options',
]; ];
public function mount() public function mount()
@@ -61,7 +63,7 @@ class General extends Component
public function instantSaveAdvanced() public function instantSaveAdvanced()
{ {
try { try {
if (! $this->server->isLogDrainEnabled()) { if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false; $this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
@@ -98,14 +100,14 @@ class General extends Component
public function instantSave() public function instantSave()
{ {
try { try {
if ($this->database->is_public && ! $this->database->public_port) { if ($this->database->is_public && !$this->database->public_port) {
$this->dispatch('error', 'Public port is required.'); $this->dispatch('error', 'Public port is required.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
if ($this->database->is_public) { if ($this->database->is_public) {
if (! str($this->database->status)->startsWith('running')) { if (!str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false; $this->database->is_public = false;
@@ -120,7 +122,7 @@ class General extends Component
$this->db_url_public = $this->database->external_db_url; $this->db_url_public = $this->database->external_db_url;
$this->database->save(); $this->database->save();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public; $this->database->is_public = !$this->database->is_public;
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -33,6 +33,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -46,6 +47,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Run Options',
]; ];
public function mount() public function mount()
@@ -59,7 +61,7 @@ class General extends Component
public function instantSaveAdvanced() public function instantSaveAdvanced()
{ {
try { try {
if (! $this->server->isLogDrainEnabled()) { if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false; $this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
@@ -99,14 +101,14 @@ class General extends Component
public function instantSave() public function instantSave()
{ {
try { try {
if ($this->database->is_public && ! $this->database->public_port) { if ($this->database->is_public && !$this->database->public_port) {
$this->dispatch('error', 'Public port is required.'); $this->dispatch('error', 'Public port is required.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
if ($this->database->is_public) { if ($this->database->is_public) {
if (! str($this->database->status)->startsWith('running')) { if (!str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false; $this->database->is_public = false;
@@ -121,7 +123,7 @@ class General extends Component
$this->db_url_public = $this->database->external_db_url; $this->db_url_public = $this->database->external_db_url;
$this->database->save(); $this->database->save();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public; $this->database->is_public = !$this->database->is_public;
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -34,6 +34,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -48,6 +49,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Run Options',
]; ];
public function mount() public function mount()
@@ -60,7 +62,7 @@ class General extends Component
public function instantSaveAdvanced() public function instantSaveAdvanced()
{ {
try { try {
if (! $this->server->isLogDrainEnabled()) { if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false; $this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
@@ -97,14 +99,14 @@ class General extends Component
public function instantSave() public function instantSave()
{ {
try { try {
if ($this->database->is_public && ! $this->database->public_port) { if ($this->database->is_public && !$this->database->public_port) {
$this->dispatch('error', 'Public port is required.'); $this->dispatch('error', 'Public port is required.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
if ($this->database->is_public) { if ($this->database->is_public) {
if (! str($this->database->status)->startsWith('running')) { if (!str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false; $this->database->is_public = false;
@@ -119,7 +121,7 @@ class General extends Component
$this->db_url_public = $this->database->external_db_url; $this->db_url_public = $this->database->external_db_url;
$this->database->save(); $this->database->save();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public; $this->database->is_public = !$this->database->is_public;
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -49,6 +49,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -65,6 +66,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Run Options',
]; ];
public function mount() public function mount()

View File

@@ -31,6 +31,7 @@ class General extends Component
'database.is_public' => 'nullable|boolean', 'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer', 'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean', 'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
]; ];
protected $validationAttributes = [ protected $validationAttributes = [
@@ -42,6 +43,7 @@ class General extends Component
'database.ports_mappings' => 'Port Mapping', 'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public', 'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port', 'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Options',
]; ];
public function mount() public function mount()
@@ -55,7 +57,7 @@ class General extends Component
public function instantSaveAdvanced() public function instantSaveAdvanced()
{ {
try { try {
if (! $this->server->isLogDrainEnabled()) { if (!$this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false; $this->database->is_log_drain_enabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.'); $this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
@@ -86,14 +88,14 @@ class General extends Component
public function instantSave() public function instantSave()
{ {
try { try {
if ($this->database->is_public && ! $this->database->public_port) { if ($this->database->is_public && !$this->database->public_port) {
$this->dispatch('error', 'Public port is required.'); $this->dispatch('error', 'Public port is required.');
$this->database->is_public = false; $this->database->is_public = false;
return; return;
} }
if ($this->database->is_public) { if ($this->database->is_public) {
if (! str($this->database->status)->startsWith('running')) { if (!str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.'); $this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false; $this->database->is_public = false;
@@ -108,7 +110,7 @@ class General extends Component
$this->db_url_public = $this->database->external_db_url; $this->db_url_public = $this->database->external_db_url;
$this->database->save(); $this->database->save();
} catch (\Throwable $e) { } catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public; $this->database->is_public = !$this->database->is_public;
return handleError($e, $this); return handleError($e, $this);
} }

View File

@@ -5,6 +5,8 @@ namespace App\Livewire\Project\New;
use App\Models\EnvironmentVariable; use App\Models\EnvironmentVariable;
use App\Models\Project; use App\Models\Project;
use App\Models\Service; use App\Models\Service;
use App\Models\StandaloneDocker;
use App\Models\SwarmDocker;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Livewire\Component; use Livewire\Component;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
@@ -58,12 +60,26 @@ 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('name', $this->parameters['environment_name'])->first();
$destination_uuid = $this->query['destination'];
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
if (! $destination) {
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
}
if (! $destination) {
throw new \Exception('Destination not found. What?!');
}
$destination_class = $destination->getMorphClass();
$service = Service::create([ $service = Service::create([
'name' => 'service'.Str::random(10), 'name' => 'service'.Str::random(10),
'docker_compose_raw' => $this->dockerComposeRaw, 'docker_compose_raw' => $this->dockerComposeRaw,
'environment_id' => $environment->id, 'environment_id' => $environment->id,
'server_id' => (int) $server_id, 'server_id' => (int) $server_id,
'destination_id' => $destination->id,
'destination_type' => $destination_class,
]); ]);
$variables = parseEnvFormatToArray($this->envFile); $variables = parseEnvFormatToArray($this->envFile);
foreach ($variables as $key => $variable) { foreach ($variables as $key => $variable) {
EnvironmentVariable::create([ EnvironmentVariable::create([

View File

@@ -45,6 +45,8 @@ class Select extends Component
public ?string $selectedEnvironment = null; public ?string $selectedEnvironment = null;
public string $postgresql_type = 'postgres:16-alpine';
public ?string $existingPostgresqlUrl = null; public ?string $existingPostgresqlUrl = null;
public ?string $search = null; public ?string $search = null;
@@ -202,6 +204,8 @@ class Select extends Component
$docker = $this->standaloneDockers->first() ?? $this->swarmDockers->first(); $docker = $this->standaloneDockers->first() ?? $this->swarmDockers->first();
if ($docker) { if ($docker) {
$this->setDestination($docker->uuid); $this->setDestination($docker->uuid);
return $this->whatToDoNext();
} }
} }
$this->current_step = 'destinations'; $this->current_step = 'destinations';
@@ -211,6 +215,28 @@ class Select extends Component
{ {
$this->destination_uuid = $destination_uuid; $this->destination_uuid = $destination_uuid;
return $this->whatToDoNext();
}
public function setPostgresqlType(string $type)
{
$this->postgresql_type = $type;
return redirect()->route('project.resource.create', [
'project_uuid' => $this->parameters['project_uuid'],
'environment_name' => $this->parameters['environment_name'],
'type' => $this->type,
'destination' => $this->destination_uuid,
'server_id' => $this->server_id,
'database_image' => $this->postgresql_type,
]);
}
public function whatToDoNext()
{
if ($this->type === 'postgresql') {
$this->current_step = 'select-postgresql-type';
} 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_name' => $this->parameters['environment_name'],
@@ -219,6 +245,7 @@ class Select extends Component
'server_id' => $this->server_id, 'server_id' => $this->server_id,
]); ]);
} }
}
public function loadServers() public function loadServers()
{ {

View File

@@ -18,6 +18,7 @@ class Create extends Component
$type = str(request()->query('type')); $type = str(request()->query('type'));
$destination_uuid = request()->query('destination'); $destination_uuid = request()->query('destination');
$server_id = request()->query('server_id'); $server_id = request()->query('server_id');
$database_image = request()->query('database_image');
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first(); $project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
if (! $project) { if (! $project) {
@@ -33,7 +34,11 @@ class Create extends Component
if (in_array($type, DATABASE_TYPES)) { if (in_array($type, DATABASE_TYPES)) {
if ($type->value() === 'postgresql') { if ($type->value() === 'postgresql') {
$database = create_standalone_postgresql($environment->id, $destination_uuid); $database = create_standalone_postgresql(
environmentId: $environment->id,
destinationUuid: $destination_uuid,
databaseImage: $database_image
);
} elseif ($type->value() === 'redis') { } elseif ($type->value() === 'redis') {
$database = create_standalone_redis($environment->id, $destination_uuid); $database = create_standalone_redis($environment->id, $destination_uuid);
} elseif ($type->value() === 'mongodb') { } elseif ($type->value() === 'mongodb') {

View File

@@ -25,6 +25,7 @@ class Configuration extends Component
return [ return [
"echo-private:user.{$userId},ServiceStatusChanged" => 'check_status', "echo-private:user.{$userId},ServiceStatusChanged" => 'check_status',
'check_status', 'check_status',
'refresh' => '$refresh',
]; ];
} }

View File

@@ -11,7 +11,11 @@ class EditCompose extends Component
public $serviceId; public $serviceId;
protected $listeners = ['refreshEnvs', 'envsUpdated']; protected $listeners = [
'refreshEnvs',
'envsUpdated',
'refresh' => 'envsUpdated',
];
protected $rules = [ protected $rules = [
'service.docker_compose_raw' => 'required', 'service.docker_compose_raw' => 'required',

View File

@@ -33,6 +33,7 @@ class FileStorage extends Component
'fileStorage.fs_path' => 'required', 'fileStorage.fs_path' => 'required',
'fileStorage.mount_path' => 'required', 'fileStorage.mount_path' => 'required',
'fileStorage.content' => 'nullable', 'fileStorage.content' => 'nullable',
'fileStorage.is_based_on_git' => 'required|boolean',
]; ];
public function mount() public function mount()
@@ -45,6 +46,7 @@ class FileStorage extends Component
$this->workdir = null; $this->workdir = null;
$this->fs_path = $this->fileStorage->fs_path; $this->fs_path = $this->fileStorage->fs_path;
} }
$this->fileStorage->loadStorageOnServer();
} }
public function convertToDirectory() public function convertToDirectory()
@@ -53,6 +55,7 @@ class FileStorage extends Component
$this->fileStorage->deleteStorageOnServer(); $this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = true; $this->fileStorage->is_directory = true;
$this->fileStorage->content = null; $this->fileStorage->content = null;
$this->fileStorage->is_based_on_git = false;
$this->fileStorage->save(); $this->fileStorage->save();
$this->fileStorage->saveStorageOnServer(); $this->fileStorage->saveStorageOnServer();
} catch (\Throwable $e) { } catch (\Throwable $e) {
@@ -68,6 +71,9 @@ class FileStorage extends Component
$this->fileStorage->deleteStorageOnServer(); $this->fileStorage->deleteStorageOnServer();
$this->fileStorage->is_directory = false; $this->fileStorage->is_directory = false;
$this->fileStorage->content = null; $this->fileStorage->content = null;
if (data_get($this->resource, 'settings.is_preserve_repository_enabled')) {
$this->fileStorage->is_based_on_git = true;
}
$this->fileStorage->save(); $this->fileStorage->save();
$this->fileStorage->saveStorageOnServer(); $this->fileStorage->saveStorageOnServer();
} catch (\Throwable $e) { } catch (\Throwable $e) {

View File

@@ -53,7 +53,7 @@ class StackForm extends Component
public function saveCompose($raw) public function saveCompose($raw)
{ {
$this->service->docker_compose_raw = $raw; $this->service->docker_compose_raw = $raw;
$this->submit(); $this->submit(notify: false);
} }
public function instantSave() public function instantSave()
@@ -62,7 +62,7 @@ class StackForm extends Component
$this->dispatch('success', 'Service settings saved.'); $this->dispatch('success', 'Service settings saved.');
} }
public function submit() public function submit($notify = true)
{ {
try { try {
$this->validate(); $this->validate();
@@ -76,7 +76,7 @@ class StackForm extends Component
$this->service->refresh(); $this->service->refresh();
$this->service->saveComposeConfigs(); $this->service->saveComposeConfigs();
$this->dispatch('refreshEnvs'); $this->dispatch('refreshEnvs');
$this->dispatch('success', 'Service saved.'); $notify && $this->dispatch('success', 'Service saved.');
} catch (\Throwable $e) { } catch (\Throwable $e) {
return handleError($e, $this); return handleError($e, $this);
} finally { } finally {

View File

@@ -23,8 +23,8 @@ class All extends Component
public string $view = 'normal'; public string $view = 'normal';
protected $listeners = [ protected $listeners = [
'refreshEnvs',
'saveKey' => 'submit', 'saveKey' => 'submit',
'environmentVariableDeleted' => 'refreshEnvs',
]; ];
protected $rules = [ protected $rules = [
@@ -40,220 +40,240 @@ class All extends Component
$this->showPreview = true; $this->showPreview = true;
} }
$this->modalId = new Cuid2; $this->modalId = new Cuid2;
$this->sortMe(); $this->sortEnvironmentVariables();
$this->getDevView();
}
public function sortMe()
{
if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose') {
if ($this->resource->settings->is_env_sorting_enabled) {
$this->resource->environment_variables = $this->resource->environment_variables->sortBy('key');
$this->resource->environment_variables_preview = $this->resource->environment_variables_preview->sortBy('key');
} else {
$this->resource->environment_variables = $this->resource->environment_variables->sortBy('id');
$this->resource->environment_variables_preview = $this->resource->environment_variables_preview->sortBy('id');
}
}
$this->getDevView();
} }
public function instantSave() public function instantSave()
{ {
if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose') {
$this->resource->settings->save(); $this->resource->settings->save();
$this->sortEnvironmentVariables();
$this->dispatch('success', 'Environment variable settings updated.'); $this->dispatch('success', 'Environment variable settings updated.');
$this->sortMe();
} }
public function sortEnvironmentVariables()
{
if ($this->resource->type() === 'application') {
$this->resource->load(['environment_variables', 'environment_variables_preview']);
} else {
$this->resource->load(['environment_variables']);
}
$sortBy = data_get($this->resource, 'settings.is_env_sorting_enabled') ? 'key' : 'order';
$sortFunction = function ($variables) use ($sortBy) {
if (!$variables) {
return $variables;
}
if ($sortBy === 'key') {
return $variables->sortBy(function ($item) {
return strtolower($item->key);
}, SORT_NATURAL | SORT_FLAG_CASE)->values();
} else {
return $variables->sortBy('order')->values();
}
};
$this->resource->environment_variables = $sortFunction($this->resource->environment_variables);
$this->resource->environment_variables_preview = $sortFunction($this->resource->environment_variables_preview);
$this->getDevView();
} }
public function getDevView() public function getDevView()
{ {
$this->variables = $this->resource->environment_variables->map(function ($item) { $this->variables = $this->formatEnvironmentVariables($this->resource->environment_variables);
if ($item->is_shown_once) {
return "$item->key=(locked secret)";
}
if ($item->is_multiline) {
return "$item->key=(multiline, edit in normal view)";
}
return "$item->key=$item->value";
})->join('
');
if ($this->showPreview) { if ($this->showPreview) {
$this->variablesPreview = $this->resource->environment_variables_preview->map(function ($item) { $this->variablesPreview = $this->formatEnvironmentVariables($this->resource->environment_variables_preview);
}
}
private function formatEnvironmentVariables($variables)
{
return $variables->map(function ($item) {
if ($item->is_shown_once) { if ($item->is_shown_once) {
return "$item->key=(locked secret)"; return "$item->key=(Locked Secret, delete and add again to change)";
} }
if ($item->is_multiline) { if ($item->is_multiline) {
return "$item->key=(multiline, edit in normal view)"; return "$item->key=(Multiline environment variable, edit in normal view)";
} }
return "$item->key=$item->value"; return "$item->key=$item->value";
})->join(' })->join("\n");
');
}
} }
public function switch() public function switch()
{ {
if ($this->view === 'normal') { $this->view = $this->view === 'normal' ? 'dev' : 'normal';
$this->view = 'dev'; $this->sortEnvironmentVariables();
} else {
$this->view = 'normal';
}
$this->sortMe();
} }
public function saveVariables($isPreview) public function submit($data = null)
{
if ($isPreview) {
$variables = parseEnvFormatToArray($this->variablesPreview);
$this->resource->environment_variables_preview()->whereNotIn('key', array_keys($variables))->delete();
} else {
$variables = parseEnvFormatToArray($this->variables);
$this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->delete();
}
foreach ($variables as $key => $variable) {
if ($isPreview) {
$found = $this->resource->environment_variables_preview()->where('key', $key)->first();
} else {
$found = $this->resource->environment_variables()->where('key', $key)->first();
}
if ($found) {
if ($found->is_shown_once || $found->is_multiline) {
continue;
}
$found->value = $variable;
// if (str($found->value)->startsWith('{{') && str($found->value)->endsWith('}}')) {
// $type = str($found->value)->after('{{')->before('.')->value;
// if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
// $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
// return;
// }
// }
$found->save();
continue;
} else {
$environment = new EnvironmentVariable;
$environment->key = $key;
$environment->value = $variable;
// if (str($environment->value)->startsWith('{{') && str($environment->value)->endsWith('}}')) {
// $type = str($environment->value)->after('{{')->before('.')->value;
// if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
// $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
// return;
// }
// }
$environment->is_build_time = false;
$environment->is_multiline = false;
$environment->is_preview = $isPreview ? true : false;
switch ($this->resource->type()) {
case 'application':
$environment->application_id = $this->resource->id;
break;
case 'standalone-postgresql':
$environment->standalone_postgresql_id = $this->resource->id;
break;
case 'standalone-redis':
$environment->standalone_redis_id = $this->resource->id;
break;
case 'standalone-mongodb':
$environment->standalone_mongodb_id = $this->resource->id;
break;
case 'standalone-mysql':
$environment->standalone_mysql_id = $this->resource->id;
break;
case 'standalone-mariadb':
$environment->standalone_mariadb_id = $this->resource->id;
break;
case 'standalone-keydb':
$environment->standalone_keydb_id = $this->resource->id;
break;
case 'standalone-dragonfly':
$environment->standalone_dragonfly_id = $this->resource->id;
break;
case 'standalone-clickhouse':
$environment->standalone_clickhouse_id = $this->resource->id;
break;
case 'service':
$environment->service_id = $this->resource->id;
break;
}
$environment->save();
}
}
if ($isPreview) {
$this->dispatch('success', 'Preview environment variables updated.');
} else {
$this->dispatch('success', 'Environment variables updated.');
}
$this->refreshEnvs();
}
public function refreshEnvs()
{
$this->resource->refresh();
$this->getDevView();
}
public function submit($data)
{ {
try { try {
if ($data === null) {
$this->handleBulkSubmit();
} else {
$this->handleSingleSubmit($data);
}
$this->updateOrder();
$this->sortEnvironmentVariables();
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
private function updateOrder()
{
$variables = parseEnvFormatToArray($this->variables);
$order = 1;
foreach ($variables as $key => $value) {
$env = $this->resource->environment_variables()->where('key', $key)->first();
if ($env) {
$env->order = $order;
$env->save();
}
$order++;
}
if ($this->showPreview) {
$previewVariables = parseEnvFormatToArray($this->variablesPreview);
$order = 1;
foreach ($previewVariables as $key => $value) {
$env = $this->resource->environment_variables_preview()->where('key', $key)->first();
if ($env) {
$env->order = $order;
$env->save();
}
$order++;
}
}
}
private function handleBulkSubmit()
{
$variables = parseEnvFormatToArray($this->variables);
$this->deleteRemovedVariables(false, $variables);
$this->updateOrCreateVariables(false, $variables);
if ($this->showPreview) {
$previewVariables = parseEnvFormatToArray($this->variablesPreview);
$this->deleteRemovedVariables(true, $previewVariables);
$this->updateOrCreateVariables(true, $previewVariables);
}
$this->dispatch('success', 'Environment variables updated.');
}
private function handleSingleSubmit($data)
{
$found = $this->resource->environment_variables()->where('key', $data['key'])->first(); $found = $this->resource->environment_variables()->where('key', $data['key'])->first();
if ($found) { if ($found) {
$this->dispatch('error', 'Environment variable already exists.'); $this->dispatch('error', 'Environment variable already exists.');
return; return;
} }
$maxOrder = $this->resource->environment_variables()->max('order') ?? 0;
$environment = $this->createEnvironmentVariable($data);
$environment->order = $maxOrder + 1;
$environment->save();
}
private function createEnvironmentVariable($data)
{
$environment = new EnvironmentVariable; $environment = new EnvironmentVariable;
$environment->key = $data['key']; $environment->key = $data['key'];
$environment->value = $data['value']; $environment->value = $data['value'];
$environment->is_build_time = $data['is_build_time']; $environment->is_build_time = $data['is_build_time'] ?? false;
$environment->is_multiline = $data['is_multiline']; $environment->is_multiline = $data['is_multiline'] ?? false;
$environment->is_literal = $data['is_literal']; $environment->is_literal = $data['is_literal'] ?? false;
$environment->is_preview = $data['is_preview']; $environment->is_preview = $data['is_preview'] ?? false;
switch ($this->resource->type()) { $resourceType = $this->resource->type();
case 'application': $resourceIdField = $this->getResourceIdField($resourceType);
$environment->application_id = $this->resource->id;
break; if ($resourceIdField) {
case 'standalone-postgresql': $environment->$resourceIdField = $this->resource->id;
$environment->standalone_postgresql_id = $this->resource->id;
break;
case 'standalone-redis':
$environment->standalone_redis_id = $this->resource->id;
break;
case 'standalone-mongodb':
$environment->standalone_mongodb_id = $this->resource->id;
break;
case 'standalone-mysql':
$environment->standalone_mysql_id = $this->resource->id;
break;
case 'standalone-mariadb':
$environment->standalone_mariadb_id = $this->resource->id;
break;
case 'standalone-keydb':
$environment->standalone_keydb_id = $this->resource->id;
break;
case 'standalone-dragonfly':
$environment->standalone_dragonfly_id = $this->resource->id;
break;
case 'standalone-clickhouse':
$environment->standalone_clickhouse_id = $this->resource->id;
break;
case 'service':
$environment->service_id = $this->resource->id;
break;
} }
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)
{
$method = $isPreview ? 'environment_variables_preview' : 'environment_variables';
$this->resource->$method()->whereNotIn('key', array_keys($variables))->delete();
}
private function updateOrCreateVariables($isPreview, $variables)
{
foreach ($variables as $key => $value) {
$method = $isPreview ? 'environment_variables_preview' : 'environment_variables';
$found = $this->resource->$method()->where('key', $key)->first();
if ($found) {
if (! $found->is_shown_once && ! $found->is_multiline) {
$found->value = $value;
$found->save();
}
} else {
$environment = new EnvironmentVariable;
$environment->key = $key;
$environment->value = $value;
$environment->is_build_time = false;
$environment->is_multiline = false;
$environment->is_preview = $isPreview;
$this->setEnvironmentResourceId($environment);
$environment->save(); $environment->save();
$this->refreshEnvs();
$this->dispatch('success', 'Environment variable added.');
} catch (\Throwable $e) {
return handleError($e, $this);
} }
} }
}
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()
{
$this->resource->refresh();
$this->sortEnvironmentVariables();
$this->getDevView();
}
} }

View File

@@ -24,7 +24,8 @@ class Show extends Component
public string $type; public string $type;
protected $listeners = [ protected $listeners = [
'refresh' => 'refresh', 'refreshEnvs' => 'refresh',
'refresh',
'compose_loaded' => '$refresh', 'compose_loaded' => '$refresh',
]; ];
@@ -129,7 +130,8 @@ class Show extends Component
{ {
try { try {
$this->env->delete(); $this->env->delete();
$this->dispatch('refreshEnvs'); $this->dispatch('environmentVariableDeleted');
$this->dispatch('success', 'Environment variable deleted successfully.');
} catch (\Exception $e) { } catch (\Exception $e) {
return handleError($e); return handleError($e);
} }

View File

@@ -97,7 +97,7 @@ class GetLogs extends Component
if (! $refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) { if (! $refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) {
return; return;
} }
if (! $this->numberOfLines) { if ($this->numberOfLines <= 0) {
$this->numberOfLines = 1000; $this->numberOfLines = 1000;
} }
if ($this->container) { if ($this->container) {

View File

@@ -26,7 +26,7 @@ class All extends Component
$this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name')); $this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name'));
} elseif ($this->resource->type() == 'application') { } elseif ($this->resource->type() == 'application') {
if ($this->resource->build_pack === 'dockercompose') { if ($this->resource->build_pack === 'dockercompose') {
$parsed = $this->resource->parseCompose(); $parsed = $this->resource->oldParser();
$containers = collect(data_get($parsed, 'services'))->keys(); $containers = collect(data_get($parsed, 'services'))->keys();
$this->containerNames = $containers; $this->containerNames = $containers;
} else { } else {

View File

@@ -16,7 +16,7 @@ class Show extends Component
public array $parameters; public array $parameters;
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey']; protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey'];
public function saveKey($data) public function saveKey($data)
{ {

View File

@@ -3,6 +3,8 @@
namespace App\Models; namespace App\Models;
use App\Enums\ApplicationDeploymentStatus; use App\Enums\ApplicationDeploymentStatus;
use App\Enums\ProxyTypes;
use App\Jobs\ServerFilesFromServerJob;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
@@ -412,23 +414,6 @@ class Application extends BaseModel
); );
} }
public function dockerComposePrLocation(): Attribute
{
return Attribute::make(
set: function ($value) {
if (is_null($value) || $value === '') {
return '/docker-compose.yaml';
} else {
if ($value !== '/') {
return Str::start(Str::replaceEnd('/', '', $value), '/');
}
return Str::start($value, '/');
}
}
);
}
public function baseDirectory(): Attribute public function baseDirectory(): Attribute
{ {
return Attribute::make( return Attribute::make(
@@ -479,12 +464,12 @@ class Application extends BaseModel
$main_server_status = $this->destination->server->isFunctional(); $main_server_status = $this->destination->server->isFunctional();
foreach ($additional_servers_status as $status) { foreach ($additional_servers_status as $status) {
$server_status = str($status)->before(':')->value(); $server_status = str($status)->before(':')->value();
if ($main_server_status !== $server_status) { if ($server_status !== 'running') {
return false; return false;
} }
} }
return true; return $main_server_status;
} }
} }
); );
@@ -1040,7 +1025,7 @@ class Application extends BaseModel
} }
} }
public function parseRawCompose() public function oldRawParser()
{ {
try { try {
$yaml = Yaml::parse($this->docker_compose_raw); $yaml = Yaml::parse($this->docker_compose_raw);
@@ -1100,8 +1085,550 @@ class Application extends BaseModel
instant_remote_process($commands, $this->destination->server, false); instant_remote_process($commands, $this->destination->server, false);
} }
public function parseCompose(int $pull_request_id = 0, ?int $preview_id = null) public function newParser(int $pull_request_id = 0, ?int $preview_id = null)
{ {
$pullRequestId = $pull_request_id;
$isPullRequest = $pullRequestId == 0 ? false : true;
$uuid = data_get($this, 'uuid');
$server = data_get($this, 'destination.server');
$compose = data_get($this, 'docker_compose_raw');
try {
$yaml = Yaml::parse($compose);
} catch (\Exception $e) {
return;
}
$services = data_get($yaml, 'services', collect([]));
$topLevel = collect([
'volumes' => collect(data_get($yaml, 'volumes', [])),
'networks' => collect(data_get($yaml, 'networks', [])),
'configs' => collect(data_get($yaml, 'configs', [])),
'secrets' => collect(data_get($yaml, 'secrets', [])),
]);
// If there are predefined volumes, make sure they are not null
if ($topLevel->get('volumes')->count() > 0) {
$temp = collect([]);
foreach ($topLevel['volumes'] as $volumeName => $volume) {
if (is_null($volume)) {
continue;
}
$temp->put($volumeName, $volume);
}
$topLevel['volumes'] = $temp;
}
// Get the base docker network
$baseNetwork = collect([$uuid]);
if ($isPullRequest) {
$baseNetwork = collect(["{$uuid}-{$pullRequestId}"]);
}
$parsedServices = collect([]);
$fileStorages = $this->fileStorages();
// Let's loop through the services
foreach ($services as $serviceName => $service) {
$image = data_get_str($service, 'image');
$restart = data_get_str($service, 'restart', RESTART_MODE);
$logging = data_get($service, 'logging');
if ($server->isLogDrainEnabled() && $this->isLogDrainEnabled()) {
$logging = [
'driver' => 'fluentd',
'options' => [
'fluentd-address' => 'tcp://127.0.0.1:24224',
'fluentd-async' => 'true',
'fluentd-sub-second-precision' => 'true',
],
];
}
$volumes = collect(data_get($service, 'volumes', []));
$networks = collect(data_get($service, 'networks', []));
$depends_on = collect(data_get($service, 'depends_on', []));
$labels = collect(data_get($service, 'labels', []));
$environment = collect(data_get($service, 'environment', []));
$buildArgs = collect(data_get($service, 'build.args', []));
$environment = $environment->merge($buildArgs);
$baseName = generateApplicationContainerName(
application: $this,
pull_request_id: $pullRequestId
);
$containerName = "$serviceName-$baseName";
$volumesParsed = collect([]);
if ($volumes->count() > 0) {
foreach ($volumes as $index => $volume) {
$type = null;
$source = null;
$target = null;
$content = null;
$isDirectory = false;
if (is_string($volume)) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
$foundConfig = $fileStorages->whereMountPath($target)->first();
if (sourceIsLocal($source)) {
$type = str('bind');
if ($foundConfig) {
$contentNotNull_temp = data_get($foundConfig, 'content');
if ($contentNotNull_temp) {
$content = $contentNotNull_temp;
}
$isDirectory = data_get($foundConfig, 'is_directory');
} else {
// By default, we cannot determine if the bind is a directory or not, so we set it to directory
$isDirectory = true;
}
} else {
$type = str('volume');
}
} elseif (is_array($volume)) {
$type = data_get_str($volume, 'type');
$source = data_get_str($volume, 'source');
$target = data_get_str($volume, 'target');
$content = data_get($volume, 'content');
$isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
$foundConfig = $fileStorages->whereMountPath($target)->first();
if ($foundConfig) {
$contentNotNull_temp = data_get($foundConfig, 'content');
if ($contentNotNull_temp) {
$content = $contentNotNull_temp;
}
$isDirectory = data_get($foundConfig, 'is_directory');
} else {
// if isDirectory is not set (or false) & content is also not set, we assume it is a directory
if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) {
$isDirectory = true;
}
}
}
if ($type->value() === 'bind') {
if ($source->value() === '/var/run/docker.sock') {
return $volume;
}
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
return $volume;
}
$mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
$source = replaceLocalSource($source, $mainDirectory);
if ($isPullRequest) {
$source = $source."-pr-$pullRequestId";
}
if (
! $this?->settings?->is_preserve_repository_enabled || $foundConfig?->is_based_on_git
) {
LocalFileVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $this->id,
'resource_type' => get_class($this),
],
[
'fs_path' => $source,
'mount_path' => $target,
'content' => $content,
'is_directory' => $isDirectory,
'resource_id' => $this->id,
'resource_type' => get_class($this),
]
);
}
$volume = "$source:$target";
} elseif ($type->value() === 'volume') {
if ($topLevel->get('volumes')->has($source->value())) {
$temp = $topLevel->get('volumes')->get($source->value());
if (data_get($temp, 'driver_opts.type') === 'cifs') {
return $volume;
}
if (data_get($temp, 'driver_opts.type') === 'nfs') {
return $volume;
}
}
$slugWithoutUuid = Str::slug($source, '-');
$name = "{$uuid}_{$slugWithoutUuid}";
if ($isPullRequest) {
$name = "{$name}-pr-$pullRequestId";
}
if (is_string($volume)) {
$source = str($volume)->before(':');
$target = str($volume)->after(':')->beforeLast(':');
$source = $name;
$volume = "$source:$target";
} elseif (is_array($volume)) {
data_set($volume, 'source', $name);
}
$topLevel->get('volumes')->put($name, [
'name' => $name,
]);
LocalPersistentVolume::updateOrCreate(
[
'mount_path' => $target,
'resource_id' => $this->id,
'resource_type' => get_class($this),
],
[
'name' => $name,
'mount_path' => $target,
'resource_id' => $this->id,
'resource_type' => get_class($this),
]
);
}
dispatch(new ServerFilesFromServerJob($this));
$volumesParsed->put($index, $volume);
}
}
if ($depends_on?->count() > 0) {
if ($isPullRequest) {
$newDependsOn = collect([]);
$depends_on->each(function ($dependency, $condition) use ($pullRequestId, $newDependsOn) {
if (is_numeric($condition)) {
$dependency = "$dependency-pr-$pullRequestId";
$newDependsOn->put($condition, $dependency);
} else {
$condition = "$condition-pr-$pullRequestId";
$newDependsOn->put($condition, $dependency);
}
});
$depends_on = $newDependsOn;
}
}
if ($topLevel->get('networks')?->count() > 0) {
foreach ($topLevel->get('networks') as $networkName => $network) {
if ($networkName === 'default') {
continue;
}
// ignore aliases
if ($network['aliases'] ?? false) {
continue;
}
$networkExists = $networks->contains(function ($value, $key) use ($networkName) {
return $value == $networkName || $key == $networkName;
});
if (! $networkExists) {
$networks->put($networkName, null);
}
}
}
$baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) {
return $value == $baseNetwork;
});
if (! $baseNetworkExists) {
foreach ($baseNetwork as $network) {
$topLevel->get('networks')->put($network, [
'name' => $network,
'external' => true,
]);
}
}
$networks_temp = collect();
foreach ($networks as $key => $network) {
if (gettype($network) === 'string') {
// networks:
// - appwrite
$networks_temp->put($network, null);
} elseif (gettype($network) === 'array') {
// networks:
// default:
// ipv4_address: 192.168.203.254
$networks_temp->put($key, $network);
}
}
foreach ($baseNetwork as $key => $network) {
$networks_temp->put($network, null);
}
if (data_get($this, 'settings.connect_to_docker_network')) {
$network = $this->destination->network;
$networks_temp->put($network, null);
$topLevel->get('networks')->put($network, [
'name' => $network,
'external' => true,
]);
}
foreach ($environment as $key => $value) {
if (is_numeric($key)) {
if (is_array($value)) {
// - SESSION_SECRET: 123
// - SESSION_SECRET:
$key = str(collect($value)->keys()->first());
$value = str(collect($value)->values()->first());
} else {
$variable = str($value);
if ($variable->contains('=')) {
// - SESSION_SECRET=123
// - SESSION_SECRET=
$key = $variable->before('=');
$value = $variable->after('=');
} else {
// - SESSION_SECRET
$key = $variable;
$value = null;
}
}
} else {
// SESSION_SECRET: 123
// SESSION_SECRET:
$key = str($key);
$value = str($value);
}
// Auto generate FQDN and URL
if ($key->startsWith('SERVICE_FQDN') || $value->startsWith('$SERVICE_FQDN') || $value->startsWith('${SERVICE_FQDN')) {
if ($value->contains('SERVICE_FQDN')) {
$key = str(replaceVariables($value));
$value = null;
}
$name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower();
$fqdn = generateFqdn($server, "{$name->value()}-{$uuid}");
$url = str($fqdn)->replace('http://', '')->replace('https://', '')->replace('www.', '');
$keyUrl = $key->replace('SERVICE_FQDN', 'SERVICE_URL');
// TODO: is this needed?
// if (substr_count($key->value(), '_') === 3) {
// // SERVICE_FQDN_UMAMI_1000
// $port = $key->afterLast('_');
// } else {
// // SERVICE_FQDN_UMAMI
// $port = null;
// }
// if ($port) {
// $fqdn = "$fqdn:$port";
// }
if ($value && get_class($value) === 'Illuminate\Support\Stringable') {
$path = $value->value();
$fqdn = "$fqdn$path";
}
// ray([
// 'key' => $key,
// 'value' => $fqdn,
// ]);
// ray($this->environment_variables()->where('key', $key)->where('application_id', $this->id)->first());
$this->environment_variables()->where('key', $key)->where('application_id', $this->id)->firstOrCreate([
'key' => $key,
'application_id' => $this->id,
], [
'value' => $fqdn,
'is_build_time' => false,
'is_preview' => false,
]);
$this->environment_variables()->where('key', $keyUrl)->where('application_id', $this->id)->firstOrCreate([
'key' => $keyUrl,
'application_id' => $this->id,
], [
'value' => $url,
'is_build_time' => false,
'is_preview' => false,
]);
} elseif ($value->startsWith('$')) {
// If the value is a variable then we will add it to Coolify's DB
// ${VARIABLE} will be VARIABLE instead
$value = str(replaceVariables($value));
if ($value->contains(':-')) {
$key = $value->before(':');
$defaultValue = $value->after(':-');
} elseif ($value->contains('-')) {
$key = $value->before('-');
$defaultValue = $value->after('-');
} elseif ($value->contains(':?')) {
$key = $value->before(':');
$defaultValue = $value->after(':?');
} elseif ($value->contains('?')) {
$key = $value->before('?');
$defaultValue = $value->after('?');
} else {
$key = $value;
$defaultValue = null;
}
$this->environment_variables()->where('key', $key)->where('application_id', $this->id)->firstOrCreate([
'key' => $key,
'application_id' => $this->id,
'is_preview' => false,
], [
'value' => $defaultValue,
'is_build_time' => false,
'is_preview' => false,
]);
}
}
$branch = $this->git_branch;
if ($pullRequestId !== 0) {
$branch = "pull/{$pullRequestId}/head";
}
if ($this->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
$environment->put('COOLIFY_BRANCH', $branch);
}
if ($this->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
$environment->put('COOLIFY_CONTAINER_NAME', $containerName);
}
// Remove SERVICE_FQDN and SERVICE_URL from environment
$environment = $environment->filter(function ($value, $key) {
return ! str($key)->startsWith('SERVICE_FQDN') && ! str($key)->startsWith('SERVICE_URL');
});
// Labels
$fqdns = collect([]);
$domains = collect(json_decode($this->docker_compose_domains)) ?? collect([]);
if ($domains->count() !== 0) {
$fqdns = data_get($domains, "$serviceName.domain");
if (! $fqdns) {
$fqdns = collect([]);
} else {
$fqdns = str($fqdns)->explode(',');
if ($isPullRequest) {
$preview = $this->previews()->find($preview_id);
$docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains')));
if ($docker_compose_domains->count() > 0) {
$found_fqdn = data_get($docker_compose_domains, "$serviceName.domain");
if ($found_fqdn) {
$fqdns = collect($found_fqdn);
} else {
$fqdns = collect([]);
}
} else {
$fqdns = $fqdns->map(function ($fqdn) use ($pullRequestId) {
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->id, $pullRequestId);
$url = Url::fromString($fqdn);
$template = $this->preview_url_template;
$host = $url->getHost();
$schema = $url->getScheme();
$random = new Cuid2;
$preview_fqdn = str_replace('{{random}}', $random, $template);
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
$preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn);
$preview_fqdn = "$schema://$preview_fqdn";
$preview->fqdn = $preview_fqdn;
$preview->save();
return $preview_fqdn;
});
}
}
$shouldGenerateLabelsExactly = $server->settings->generate_exact_labels;
if ($shouldGenerateLabelsExactly) {
switch ($server->proxyType()) {
case ProxyTypes::TRAEFIK->value:
$labels = $labels->merge(
fqdnLabelsForTraefik(
uuid: $this->uuid,
domains: $fqdns,
serviceLabels: $labels,
generate_unique_uuid: $this->build_pack === 'dockercompose',
image: $image,
is_force_https_enabled: $this->isForceHttpsEnabled(),
is_gzip_enabled: $this->isGzipEnabled(),
is_stripprefix_enabled: $this->isStripprefixEnabled(),
)
);
break;
case ProxyTypes::CADDY->value:
$labels = $labels->merge(
fqdnLabelsForCaddy(
network: $this->destination->network,
uuid: $this->uuid,
domains: $fqdns,
serviceLabels: $labels,
image: $image,
is_force_https_enabled: $this->isForceHttpsEnabled(),
is_gzip_enabled: $this->isGzipEnabled(),
is_stripprefix_enabled: $this->isStripprefixEnabled(),
)
);
break;
}
} else {
$labels = $labels->merge(
fqdnLabelsForTraefik(
uuid: $this->uuid,
domains: $fqdns,
serviceLabels: $labels,
generate_unique_uuid: $this->build_pack === 'dockercompose',
image: $image,
is_force_https_enabled: $this->isForceHttpsEnabled(),
is_gzip_enabled: $this->isGzipEnabled(),
is_stripprefix_enabled: $this->isStripprefixEnabled(),
)
);
$labels = $labels->merge(
fqdnLabelsForCaddy(
network: $this->destination->network,
uuid: $this->uuid,
domains: $fqdns,
serviceLabels: $labels,
image: $image,
is_force_https_enabled: $this->isForceHttpsEnabled(),
is_gzip_enabled: $this->isGzipEnabled(),
is_stripprefix_enabled: $this->isStripprefixEnabled(),
)
);
}
}
}
$defaultLabels = defaultLabels(
id: $this->id,
name: $containerName,
pull_request_id: $pullRequestId,
type: 'application');
$labels = $labels->merge($defaultLabels);
if ($labels->count() > 0 && $this->settings->is_container_label_escape_enabled) {
$labels = $labels->map(function ($value, $key) {
return escapeDollarSign($value);
});
}
$payload = collect($service)->merge([
'restart' => $restart->value(),
'container_name' => $containerName,
'volumes' => $volumesParsed,
'networks' => $networks_temp,
'labels' => $labels,
'environment' => $environment,
]);
if ($logging) {
$payload['logging'] = $logging;
}
if ($depends_on->count() > 0) {
$payload['depends_on'] = $depends_on;
}
if ($isPullRequest) {
$serviceName = "{$serviceName}-pr-{$pullRequestId}";
}
$parsedServices->put($serviceName, $payload);
}
$topLevel->put('services', $parsedServices);
$customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets'];
$topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) {
return array_search($key, $customOrder);
});
$this->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2);
data_forget($this, 'environment_variables');
data_forget($this, 'environment_variables_preview');
$this->save();
return $topLevel;
}
public function oldParser(int $pull_request_id = 0, ?int $preview_id = null)
{
if ($this->compose_parsing_version === '3') {
return $this->newParser($pull_request_id, $preview_id);
} else
if ($this->docker_compose_raw) { if ($this->docker_compose_raw) {
return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id); return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);
} else { } else {
@@ -1154,7 +1681,7 @@ class Application extends BaseModel
if ($composeFileContent) { if ($composeFileContent) {
$this->docker_compose_raw = $composeFileContent; $this->docker_compose_raw = $composeFileContent;
$this->save(); $this->save();
$parsedServices = $this->parseCompose(); $parsedServices = $this->oldParser();
if ($this->docker_compose_domains) { if ($this->docker_compose_domains) {
$json = collect(json_decode($this->docker_compose_domains)); $json = collect(json_decode($this->docker_compose_domains));
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray(); $names = collect(data_get($parsedServices, 'services'))->keys()->toArray();

View File

@@ -14,7 +14,7 @@ class ApplicationPreview extends BaseModel
static::deleting(function ($preview) { static::deleting(function ($preview) {
if ($preview->application->build_pack === 'dockercompose') { if ($preview->application->build_pack === 'dockercompose') {
$server = $preview->application->destination->server; $server = $preview->application->destination->server;
$composeFile = $preview->application->parseCompose(pull_request_id: $preview->pull_request_id); $composeFile = $preview->application->oldParser(pull_request_id: $preview->pull_request_id);
$volumes = data_get($composeFile, 'volumes'); $volumes = data_get($composeFile, 'volumes');
$networks = data_get($composeFile, 'networks'); $networks = data_get($composeFile, 'networks');
$networkKeys = collect($networks)->keys(); $networkKeys = collect($networks)->keys();

View File

@@ -6,7 +6,6 @@ use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Model;
use OpenApi\Attributes as OA; use OpenApi\Attributes as OA;
use Symfony\Component\Yaml\Yaml;
use Visus\Cuid2\Cuid2; use Visus\Cuid2\Cuid2;
#[OA\Schema( #[OA\Schema(
@@ -97,8 +96,22 @@ class EnvironmentVariable extends Model
$resource = Application::find($this->application_id); $resource = Application::find($this->application_id);
} elseif ($this->service_id) { } elseif ($this->service_id) {
$resource = Service::find($this->service_id); $resource = Service::find($this->service_id);
} elseif ($this->database_id) { } elseif ($this->standalone_postgresql_id) {
$resource = getResourceByUuid($this->parameters['database_uuid'], data_get(auth()->user()->currentTeam(), '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; return $resource;
@@ -122,63 +135,6 @@ class EnvironmentVariable extends Model
); );
} }
protected function isFoundInCompose(): Attribute
{
return Attribute::make(
get: function () {
if (! $this->application_id) {
return true;
}
$found_in_compose = false;
$found_in_args = false;
$resource = $this->resource();
$compose = data_get($resource, 'docker_compose_raw');
if (! $compose) {
return true;
}
$yaml = Yaml::parse($compose);
$services = collect(data_get($yaml, 'services'));
if ($services->isEmpty()) {
return false;
}
foreach ($services as $service) {
$environments = collect(data_get($service, 'environment'));
$args = collect(data_get($service, 'build.args'));
if ($environments->isEmpty() && $args->isEmpty()) {
$found_in_compose = false;
break;
}
$found_in_compose = $environments->contains(function ($item) {
if (str($item)->contains('=')) {
$item = str($item)->before('=');
}
return strpos($item, $this->key) !== false;
});
if ($found_in_compose) {
break;
}
$found_in_args = $args->contains(function ($item) {
if (str($item)->contains('=')) {
$item = str($item)->before('=');
}
return strpos($item, $this->key) !== false;
});
if ($found_in_args) {
break;
}
}
return $found_in_compose || $found_in_args;
}
);
}
protected function isShared(): Attribute protected function isShared(): Attribute
{ {
return Attribute::make( return Attribute::make(
@@ -201,8 +157,10 @@ class EnvironmentVariable extends Model
$environment_variable = trim($environment_variable); $environment_variable = trim($environment_variable);
$sharedEnvsFound = str($environment_variable)->matchAll('/{{(.*?)}}/'); $sharedEnvsFound = str($environment_variable)->matchAll('/{{(.*?)}}/');
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)) {

View File

@@ -24,8 +24,9 @@ class LocalFileVolume extends BaseModel
return $this->morphTo('resource'); return $this->morphTo('resource');
} }
public function deleteStorageOnServer() public function loadStorageOnServer()
{ {
$this->load(['service']);
$isService = data_get($this->resource, 'service'); $isService = data_get($this->resource, 'service');
if ($isService) { if ($isService) {
$workdir = $this->resource->service->workdir(); $workdir = $this->resource->service->workdir();
@@ -35,17 +36,46 @@ class LocalFileVolume extends BaseModel
$server = $this->resource->destination->server; $server = $this->resource->destination->server;
} }
$commands = collect([]); $commands = collect([]);
$fs_path = data_get($this, 'fs_path'); $path = data_get_str($this, 'fs_path');
$isFile = instant_remote_process(["test -f $fs_path && echo OK || echo NOK"], $server); if ($path->startsWith('.')) {
$isDir = instant_remote_process(["test -d $fs_path && echo OK || echo NOK"], $server); $path = $path->after('.');
if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') { $path = $workdir.$path;
ray($isFile, $isDir); }
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
if ($isFile === 'OK') { if ($isFile === 'OK') {
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true"); $content = instant_remote_process(["cat $path"], $server, false);
$this->content = $content;
$this->is_directory = false;
$this->save();
}
}
public function deleteStorageOnServer()
{
$this->load(['service']);
$isService = data_get($this->resource, 'service');
if ($isService) {
$workdir = $this->resource->service->workdir();
$server = $this->resource->service->server;
} else {
$workdir = $this->resource->workdir();
$server = $this->resource->destination->server;
}
$commands = collect([]);
$path = data_get_str($this, 'fs_path');
if ($path->startsWith('.')) {
$path = $path->after('.');
$path = $workdir.$path;
}
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
if ($path && $path != '/' && $path != '.' && $path != '..') {
if ($isFile === 'OK') {
$commands->push("rm -rf $path > /dev/null 2>&1 || true");
} elseif ($isDir === 'OK') { } elseif ($isDir === 'OK') {
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true"); $commands->push("rm -rf $path > /dev/null 2>&1 || true");
$commands->push("rmdir $fs_path > /dev/null 2>&1 || true"); $commands->push("rmdir $path > /dev/null 2>&1 || true");
} }
} }
if ($commands->count() > 0) { if ($commands->count() > 0) {
@@ -55,6 +85,7 @@ class LocalFileVolume extends BaseModel
public function saveStorageOnServer() public function saveStorageOnServer()
{ {
$this->load(['service']);
$isService = data_get($this->resource, 'service'); $isService = data_get($this->resource, 'service');
if ($isService) { if ($isService) {
$workdir = $this->resource->service->workdir(); $workdir = $this->resource->service->workdir();
@@ -74,30 +105,36 @@ class LocalFileVolume extends BaseModel
$commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true"); $commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true");
} }
} }
$fileVolume = $this; $path = data_get_str($this, 'fs_path');
$path = str(data_get($fileVolume, 'fs_path')); $content = data_get($this, 'content');
$content = data_get($fileVolume, 'content');
if ($path->startsWith('.')) { if ($path->startsWith('.')) {
$path = $path->after('.'); $path = $path->after('.');
$path = $workdir.$path; $path = $workdir.$path;
} }
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server); $isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server); $isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
if ($isFile == 'OK' && $fileVolume->is_directory) { if ($isFile == 'OK' && $this->is_directory) {
$content = instant_remote_process(["cat $path"], $server, false); $content = instant_remote_process(["cat $path"], $server, false);
$fileVolume->is_directory = false; $this->is_directory = false;
$fileVolume->content = $content; $this->content = $content;
$fileVolume->save(); $this->save();
FileStorageChanged::dispatch(data_get($server, 'team_id')); FileStorageChanged::dispatch(data_get($server, 'team_id'));
throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.'); throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.');
} elseif ($isDir == 'OK' && ! $fileVolume->is_directory) { } elseif ($isDir == 'OK' && ! $this->is_directory) {
$fileVolume->is_directory = true; if ($path == '/' || $path == '.' || $path == '..' || $path == '' || str($path)->isEmpty() || is_null($path)) {
$fileVolume->save(); $this->is_directory = true;
$this->save();
throw new \Exception('The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.'); throw new \Exception('The following file is a directory on the server, but you are trying to mark it as a file. <br><br>Please delete the directory on the server or mark it as directory.');
} }
if ($isDir == 'NOK' && ! $fileVolume->is_directory) { instant_remote_process([
$chmod = data_get($fileVolume, 'chmod'); "rm -fr $path",
$chown = data_get($fileVolume, 'chown'); "touch $path",
], $server, false);
FileStorageChanged::dispatch(data_get($server, 'team_id'));
}
if ($isDir == 'NOK' && ! $this->is_directory) {
$chmod = data_get($this, 'chmod');
$chown = data_get($this, 'chown');
if ($content) { if ($content) {
$content = base64_encode($content); $content = base64_encode($content);
$commands->push("echo '$content' | base64 -d | tee $path > /dev/null"); $commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
@@ -111,7 +148,7 @@ class LocalFileVolume extends BaseModel
if ($chmod) { if ($chmod) {
$commands->push("chmod $chmod $path"); $commands->push("chmod $chmod $path");
} }
} elseif ($isDir == 'NOK' && $fileVolume->is_directory) { } elseif ($isDir == 'NOK' && $this->is_directory) {
$commands->push("mkdir -p $path > /dev/null 2>&1 || true"); $commands->push("mkdir -p $path > /dev/null 2>&1 || true");
} }

View File

@@ -26,6 +26,6 @@ class ScheduledTask extends BaseModel
public function executions(): HasMany public function executions(): HasMany
{ {
return $this->hasMany(ScheduledTaskExecution::class); return $this->hasMany(ScheduledTaskExecution::class)->orderBy('created_at', 'desc');
} }
} }

View File

@@ -649,7 +649,7 @@ $schema://$host {
} }
} }
public function getDiskUsage() public function getDiskUsage(): ?string
{ {
return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false); return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false);
} }

View File

@@ -2,11 +2,13 @@
namespace App\Models; namespace App\Models;
use App\Enums\ProxyTypes;
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use OpenApi\Attributes as OA; use OpenApi\Attributes as OA;
use Spatie\Url\Url; use Spatie\Url\Url;
use Symfony\Component\Yaml\Yaml; use Symfony\Component\Yaml\Yaml;
@@ -23,6 +25,7 @@ use Symfony\Component\Yaml\Yaml;
'description' => ['type' => 'string', 'description' => 'The description of the service.'], 'description' => ['type' => 'string', 'description' => 'The description of the service.'],
'docker_compose_raw' => ['type' => 'string', 'description' => 'The raw docker-compose.yml file of the service.'], 'docker_compose_raw' => ['type' => 'string', 'description' => 'The raw docker-compose.yml file of the service.'],
'docker_compose' => ['type' => 'string', 'description' => 'The docker-compose.yml file that is parsed and modified by Coolify.'], 'docker_compose' => ['type' => 'string', 'description' => 'The docker-compose.yml file that is parsed and modified by Coolify.'],
'destination_type' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'], 'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'], 'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'], 'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'],
@@ -205,6 +208,41 @@ class Service extends BaseModel
foreach ($applications as $application) { foreach ($applications as $application) {
$image = str($application->image)->before(':')->value(); $image = str($application->image)->before(':')->value();
switch ($image) { switch ($image) {
case str($image)?->contains('rabbitmq'):
$data = collect([]);
$host_port = $this->environment_variables()->where('key', 'PORT')->first();
$username = $this->environment_variables()->where('key', 'SERVICE_USER_RABBITMQ')->first();
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_RABBITMQ')->first();
if ($host_port) {
$data = $data->merge([
'Host Port Binding' => [
'key' => data_get($host_port, 'key'),
'value' => data_get($host_port, 'value'),
'rules' => 'required',
],
]);
}
if ($username) {
$data = $data->merge([
'Username' => [
'key' => data_get($username, 'key'),
'value' => data_get($username, 'value'),
'rules' => 'required',
],
]);
}
if ($password) {
$data = $data->merge([
'Password' => [
'key' => data_get($password, 'key'),
'value' => data_get($password, 'value'),
'rules' => 'required',
'isPassword' => true,
],
]);
}
$fields->put('RabbitMQ', $data->toArray());
break;
case str($image)?->contains('tolgee'): case str($image)?->contains('tolgee'):
$data = collect([]); $data = collect([]);
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first(); $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first();
@@ -504,6 +542,9 @@ class Service extends BaseModel
default: default:
$data = collect([]); $data = collect([]);
$admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first(); $admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
// Chaskiq
$admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first(); $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
if ($admin_user) { if ($admin_user) {
$data = $data->merge([ $data = $data->merge([
@@ -525,6 +566,15 @@ class Service extends BaseModel
], ],
]); ]);
} }
if ($admin_email) {
$data = $data->merge([
'Email' => [
'key' => 'ADMIN_EMAIL',
'value' => data_get($admin_email, 'value'),
'rules' => 'required|email',
],
]);
}
$fields->put('Admin', $data->toArray()); $fields->put('Admin', $data->toArray());
break; break;
case str($image)?->contains('vaultwarden'): case str($image)?->contains('vaultwarden'):

View File

@@ -0,0 +1,67 @@
<?php
namespace App\Providers;
use App\Models\User;
use Illuminate\Support\Facades\Gate;
use Laravel\Telescope\IncomingEntry;
use Laravel\Telescope\Telescope;
use Laravel\Telescope\TelescopeApplicationServiceProvider;
class TelescopeServiceProvider extends TelescopeApplicationServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
// Telescope::night();
$this->hideSensitiveRequestDetails();
$isLocal = $this->app->environment('local');
Telescope::filter(function (IncomingEntry $entry) use ($isLocal) {
return $isLocal ||
$entry->isReportableException() ||
$entry->isFailedRequest() ||
$entry->isFailedJob() ||
$entry->isScheduledTask() ||
$entry->hasMonitoredTag();
});
}
/**
* Prevent sensitive request details from being logged by Telescope.
*/
protected function hideSensitiveRequestDetails(): void
{
if ($this->app->environment('local')) {
return;
}
Telescope::hideRequestParameters(['_token']);
Telescope::hideRequestHeaders([
'cookie',
'x-csrf-token',
'x-xsrf-token',
]);
}
/**
* Register the Telescope gate.
*
* This gate determines who can access Telescope in non-local environments.
*/
protected function gate(): void
{
Gate::define('viewTelescope', function ($user) {
$root_user = User::find(0);
return in_array($user->email, [
$root_user->email,
]);
});
}
}

View File

@@ -91,7 +91,7 @@ function next_queuable(string $server_id, string $application_id): bool
$server = Server::find($server_id); $server = Server::find($server_id);
$concurrent_builds = $server->settings->concurrent_builds; $concurrent_builds = $server->settings->concurrent_builds;
ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}"); ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}")->green();
if ($deployments->count() > $concurrent_builds) { if ($deployments->count() > $concurrent_builds) {
return false; return false;

View File

@@ -19,7 +19,7 @@ function generate_database_name(string $type): string
return $type.'-database-'.$cuid; return $type.'-database-'.$cuid;
} }
function create_standalone_postgresql($environmentId, $destinationUuid, ?array $otherData = null): StandalonePostgresql function create_standalone_postgresql($environmentId, $destinationUuid, ?array $otherData = null, string $databaseImage = 'postgres:16-alpine'): StandalonePostgresql
{ {
$destination = StandaloneDocker::where('uuid', $destinationUuid)->first(); $destination = StandaloneDocker::where('uuid', $destinationUuid)->first();
if (! $destination) { if (! $destination) {
@@ -27,6 +27,7 @@ function create_standalone_postgresql($environmentId, $destinationUuid, ?array $
} }
$database = new StandalonePostgresql; $database = new StandalonePostgresql;
$database->name = generate_database_name('postgresql'); $database->name = generate_database_name('postgresql');
$database->image = $databaseImage;
$database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false); $database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
$database->environment_id = $environmentId; $database->environment_id = $environmentId;
$database->destination_id = $destination->id; $database->destination_id = $destination->id;

View File

@@ -140,6 +140,8 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
function generateApplicationContainerName(Application $application, $pull_request_id = 0) function generateApplicationContainerName(Application $application, $pull_request_id = 0)
{ {
// TODO: refactor generateApplicationContainerName, we do not need $application and $pull_request_id
$consistent_container_name = $application->settings->is_consistent_container_name_enabled; $consistent_container_name = $application->settings->is_consistent_container_name_enabled;
$now = now()->format('Hisu'); $now = now()->format('Hisu');
if ($pull_request_id !== 0 && $pull_request_id !== null) { if ($pull_request_id !== 0 && $pull_request_id !== null) {
@@ -677,18 +679,19 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
'--sysctl', '--sysctl',
'--ulimit', '--ulimit',
'--device', '--device',
'--shm-size',
]); ]);
$mapping = collect([ $mapping = collect([
'--cap-add' => 'cap_add', '--cap-add' => 'cap_add',
'--cap-drop' => 'cap_drop', '--cap-drop' => 'cap_drop',
'--security-opt' => 'security_opt', '--security-opt' => 'security_opt',
'--sysctl' => 'sysctls', '--sysctl' => 'sysctls',
'--ulimit' => 'ulimits',
'--device' => 'devices', '--device' => 'devices',
'--init' => 'init', '--init' => 'init',
'--ulimit' => 'ulimits', '--ulimit' => 'ulimits',
'--privileged' => 'privileged', '--privileged' => 'privileged',
'--ip' => 'ip', '--ip' => 'ip',
'--shm-size' => 'shm_size',
]); ]);
foreach ($matches as $match) { foreach ($matches as $match) {
$option = $match[1]; $option = $match[1];
@@ -704,6 +707,7 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
$options = collect($options); $options = collect($options);
// Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js // Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js
foreach ($options as $option => $value) { foreach ($options as $option => $value) {
// ray($option,$value);
if (! data_get($mapping, $option)) { if (! data_get($mapping, $option)) {
continue; continue;
} }
@@ -728,6 +732,10 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
} }
}); });
$compose_options->put($mapping[$option], $ulimits); $compose_options->put($mapping[$option], $ulimits);
} elseif ($option === '--shm-size') {
if (! is_null($value) && is_array($value) && count($value) > 0) {
$compose_options->put($mapping[$option], $value[0]);
}
} else { } else {
if ($list_options->contains($option)) { if ($list_options->contains($option)) {
if ($compose_options->has($mapping[$option])) { if ($compose_options->has($mapping[$option])) {
@@ -749,6 +757,26 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
return $compose_options->toArray(); return $compose_options->toArray();
} }
function generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $network)
{
$ipv4 = data_get($docker_run_options, 'ip.0');
$ipv6 = data_get($docker_run_options, 'ip6.0');
data_forget($docker_run_options, 'ip');
data_forget($docker_run_options, 'ip6');
if ($ipv4 || $ipv6) {
data_forget($docker_compose['services'][$container_name], 'networks');
}
if ($ipv4) {
$docker_compose['services'][$container_name]['networks'][$network]['ipv4_address'] = $ipv4;
}
if ($ipv6) {
$docker_compose['services'][$container_name]['networks'][$network]['ipv6_address'] = $ipv6;
}
$docker_compose['services'][$container_name] = array_merge_recursive($docker_compose['services'][$container_name], $docker_run_options);
return $docker_compose;
}
function validateComposeFile(string $compose, int $server_id): string|Throwable function validateComposeFile(string $compose, int $server_id): string|Throwable
{ {
return 'OK'; return 'OK';

View File

@@ -151,7 +151,7 @@ function generate_default_proxy_configuration(Server $server)
'services' => [ 'services' => [
'traefik' => [ 'traefik' => [
'container_name' => 'coolify-proxy', 'container_name' => 'coolify-proxy',
'image' => 'traefik:v2.11', 'image' => 'traefik:v3.1',
'restart' => RESTART_MODE, 'restart' => RESTART_MODE,
'extra_hosts' => [ 'extra_hosts' => [
'host.docker.internal:host-gateway', 'host.docker.internal:host-gateway',

View File

@@ -170,7 +170,7 @@ function generateSshCommand(Server $server, string $command)
// ray($ssh_command); // ray($ssh_command);
return $ssh_command; return $ssh_command;
} }
function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false) function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false): ?string
{ {
$timeout = config('constants.ssh.command_timeout'); $timeout = config('constants.ssh.command_timeout');
if ($command instanceof Collection) { if ($command instanceof Collection) {

View File

@@ -53,7 +53,9 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli
if ($isFile == 'OK') { if ($isFile == 'OK') {
// If its a file & exists // If its a file & exists
$filesystemContent = instant_remote_process(["cat $fileLocation"], $server); $filesystemContent = instant_remote_process(["cat $fileLocation"], $server);
if ($fileVolume->is_based_on_git) {
$fileVolume->content = $filesystemContent; $fileVolume->content = $filesystemContent;
}
$fileVolume->is_directory = false; $fileVolume->is_directory = false;
$fileVolume->save(); $fileVolume->save();
} elseif ($isDir == 'OK') { } elseif ($isDir == 'OK') {

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,7 @@
"laravel/prompts": "^0.1.6", "laravel/prompts": "^0.1.6",
"laravel/sanctum": "^v4.0", "laravel/sanctum": "^v4.0",
"laravel/socialite": "^v5.14.0", "laravel/socialite": "^v5.14.0",
"laravel/telescope": "^5.2",
"laravel/tinker": "^v2.8.1", "laravel/tinker": "^v2.8.1",
"laravel/ui": "^4.2", "laravel/ui": "^4.2",
"lcobucci/jwt": "^5.0.0", "lcobucci/jwt": "^5.0.0",
@@ -93,7 +94,9 @@
}, },
"extra": { "extra": {
"laravel": { "laravel": {
"dont-discover": [] "dont-discover": [
"laravel/telescope"
]
} }
}, },
"config": { "config": {

71
composer.lock generated
View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "cb17445966de6094aef5a92ee59d1d77", "content-hash": "213fa37e1cc796508e32069c26f3c84a",
"packages": [ "packages": [
{ {
"name": "amphp/amp", "name": "amphp/amp",
@@ -3388,6 +3388,75 @@
}, },
"time": "2024-06-28T20:09:34+00:00" "time": "2024-06-28T20:09:34+00:00"
}, },
{
"name": "laravel/telescope",
"version": "v5.2.1",
"source": {
"type": "git",
"url": "https://github.com/laravel/telescope.git",
"reference": "c3a794ae6e011c8410e4de7c49ddab079ab846b0"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/laravel/telescope/zipball/c3a794ae6e011c8410e4de7c49ddab079ab846b0",
"reference": "c3a794ae6e011c8410e4de7c49ddab079ab846b0",
"shasum": ""
},
"require": {
"ext-json": "*",
"laravel/framework": "^8.37|^9.0|^10.0|^11.0",
"php": "^8.0",
"symfony/console": "^5.3|^6.0|^7.0",
"symfony/var-dumper": "^5.0|^6.0|^7.0"
},
"require-dev": {
"ext-gd": "*",
"guzzlehttp/guzzle": "^6.0|^7.0",
"laravel/octane": "^1.4|^2.0|dev-develop",
"orchestra/testbench": "^6.40|^7.37|^8.17|^9.0",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^9.0|^10.5"
},
"type": "library",
"extra": {
"laravel": {
"providers": [
"Laravel\\Telescope\\TelescopeServiceProvider"
]
}
},
"autoload": {
"psr-4": {
"Laravel\\Telescope\\": "src/",
"Laravel\\Telescope\\Database\\Factories\\": "database/factories/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Taylor Otwell",
"email": "taylor@laravel.com"
},
{
"name": "Mohamed Said",
"email": "mohamed@laravel.com"
}
],
"description": "An elegant debug assistant for the Laravel framework.",
"keywords": [
"debugging",
"laravel",
"monitoring"
],
"support": {
"issues": "https://github.com/laravel/telescope/issues",
"source": "https://github.com/laravel/telescope/tree/v5.2.1"
},
"time": "2024-08-20T14:54:41+00:00"
},
{ {
"name": "laravel/tinker", "name": "laravel/tinker",
"version": "v2.9.0", "version": "v2.9.0",

View File

@@ -199,6 +199,7 @@ return [
App\Providers\EventServiceProvider::class, App\Providers\EventServiceProvider::class,
App\Providers\HorizonServiceProvider::class, App\Providers\HorizonServiceProvider::class,
App\Providers\RouteServiceProvider::class, App\Providers\RouteServiceProvider::class,
App\Providers\TelescopeServiceProvider::class,
], ],

View File

@@ -7,7 +7,7 @@ return [
// The release version of your application // The release version of your application
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD')) // Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
'release' => '4.0.0-beta.323', 'release' => '4.0.0-beta.324',
// When left empty or `null` the Laravel environment will be used // When left empty or `null` the Laravel environment will be used
'environment' => config('app.env'), 'environment' => config('app.env'),

206
config/telescope.php Normal file
View File

@@ -0,0 +1,206 @@
<?php
use Laravel\Telescope\Http\Middleware\Authorize;
use Laravel\Telescope\Watchers;
return [
/*
|--------------------------------------------------------------------------
| Telescope Master Switch
|--------------------------------------------------------------------------
|
| This option may be used to disable all Telescope watchers regardless
| of their individual configuration, which simply provides a single
| and convenient way to enable or disable Telescope data storage.
|
*/
'enabled' => env('TELESCOPE_ENABLED', false),
/*
|--------------------------------------------------------------------------
| Telescope Domain
|--------------------------------------------------------------------------
|
| This is the subdomain where Telescope will be accessible from. If the
| setting is null, Telescope will reside under the same domain as the
| application. Otherwise, this value will be used as the subdomain.
|
*/
'domain' => env('TELESCOPE_DOMAIN'),
/*
|--------------------------------------------------------------------------
| Telescope Path
|--------------------------------------------------------------------------
|
| This is the URI path where Telescope will be accessible from. Feel free
| to change this path to anything you like. Note that the URI will not
| affect the paths of its internal API that aren't exposed to users.
|
*/
'path' => env('TELESCOPE_PATH', 'telescope'),
/*
|--------------------------------------------------------------------------
| Telescope Storage Driver
|--------------------------------------------------------------------------
|
| This configuration options determines the storage driver that will
| be used to store Telescope's data. In addition, you may set any
| custom options as needed by the particular driver you choose.
|
*/
'driver' => env('TELESCOPE_DRIVER', 'database'),
'storage' => [
'database' => [
'connection' => env('DB_CONNECTION', 'pgsql'),
'chunk' => 1000,
],
],
/*
|--------------------------------------------------------------------------
| Telescope Queue
|--------------------------------------------------------------------------
|
| This configuration options determines the queue connection and queue
| which will be used to process ProcessPendingUpdate jobs. This can
| be changed if you would prefer to use a non-default connection.
|
*/
'queue' => [
'connection' => env('TELESCOPE_QUEUE_CONNECTION', null),
'queue' => env('TELESCOPE_QUEUE', null),
],
/*
|--------------------------------------------------------------------------
| Telescope Route Middleware
|--------------------------------------------------------------------------
|
| These middleware will be assigned to every Telescope route, giving you
| the chance to add your own middleware to this list or change any of
| the existing middleware. Or, you can simply stick with this list.
|
*/
'middleware' => [
'web',
Authorize::class,
],
/*
|--------------------------------------------------------------------------
| Allowed / Ignored Paths & Commands
|--------------------------------------------------------------------------
|
| The following array lists the URI paths and Artisan commands that will
| not be watched by Telescope. In addition to this list, some Laravel
| commands, like migrations and queue commands, are always ignored.
|
*/
'only_paths' => [
// 'api/*'
],
'ignore_paths' => [
'livewire*',
'nova-api*',
'pulse*',
'broadcasting/auth',
],
'ignore_commands' => [
//
],
/*
|--------------------------------------------------------------------------
| Telescope Watchers
|--------------------------------------------------------------------------
|
| The following array lists the "watchers" that will be registered with
| Telescope. The watchers gather the application's profile data when
| a request or task is executed. Feel free to customize this list.
|
*/
'watchers' => [
Watchers\BatchWatcher::class => env('TELESCOPE_BATCH_WATCHER', true),
Watchers\CacheWatcher::class => [
'enabled' => env('TELESCOPE_CACHE_WATCHER', true),
'hidden' => [],
],
Watchers\ClientRequestWatcher::class => env('TELESCOPE_CLIENT_REQUEST_WATCHER', true),
Watchers\CommandWatcher::class => [
'enabled' => env('TELESCOPE_COMMAND_WATCHER', true),
'ignore' => [],
],
Watchers\DumpWatcher::class => [
'enabled' => env('TELESCOPE_DUMP_WATCHER', true),
'always' => env('TELESCOPE_DUMP_WATCHER_ALWAYS', false),
],
Watchers\EventWatcher::class => [
'enabled' => env('TELESCOPE_EVENT_WATCHER', true),
'ignore' => [],
],
Watchers\ExceptionWatcher::class => env('TELESCOPE_EXCEPTION_WATCHER', true),
Watchers\GateWatcher::class => [
'enabled' => env('TELESCOPE_GATE_WATCHER', false),
'ignore_abilities' => [],
'ignore_packages' => true,
'ignore_paths' => [],
],
Watchers\JobWatcher::class => env('TELESCOPE_JOB_WATCHER', false),
Watchers\LogWatcher::class => [
'enabled' => env('TELESCOPE_LOG_WATCHER', true),
'level' => 'debug',
],
Watchers\MailWatcher::class => env('TELESCOPE_MAIL_WATCHER', false),
Watchers\ModelWatcher::class => [
'enabled' => env('TELESCOPE_MODEL_WATCHER', true),
'events' => ['eloquent.*'],
'hydrations' => true,
],
Watchers\NotificationWatcher::class => env('TELESCOPE_NOTIFICATION_WATCHER', false),
Watchers\QueryWatcher::class => [
'enabled' => env('TELESCOPE_QUERY_WATCHER', true),
'ignore_packages' => true,
'ignore_paths' => [],
'slow' => 100,
],
Watchers\RedisWatcher::class => env('TELESCOPE_REDIS_WATCHER', true),
Watchers\RequestWatcher::class => [
'enabled' => env('TELESCOPE_REQUEST_WATCHER', true),
'size_limit' => env('TELESCOPE_RESPONSE_SIZE_LIMIT', 64),
'ignore_http_methods' => [],
'ignore_status_codes' => [],
],
Watchers\ScheduleWatcher::class => env('TELESCOPE_SCHEDULE_WATCHER', false),
Watchers\ViewWatcher::class => env('TELESCOPE_VIEW_WATCHER', true),
],
];

View File

@@ -1,3 +1,3 @@
<?php <?php
return '4.0.0-beta.323'; return '4.0.0-beta.324';

View File

@@ -0,0 +1,70 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Get the migration connection name.
*/
public function getConnection(): ?string
{
return config('telescope.storage.database.connection');
}
/**
* Run the migrations.
*/
public function up(): void
{
$schema = Schema::connection($this->getConnection());
$schema->create('telescope_entries', function (Blueprint $table) {
$table->bigIncrements('sequence');
$table->uuid('uuid');
$table->uuid('batch_id');
$table->string('family_hash')->nullable();
$table->boolean('should_display_on_index')->default(true);
$table->string('type', 20);
$table->longText('content');
$table->dateTime('created_at')->nullable();
$table->unique('uuid');
$table->index('batch_id');
$table->index('family_hash');
$table->index('created_at');
$table->index(['type', 'should_display_on_index']);
});
$schema->create('telescope_entries_tags', function (Blueprint $table) {
$table->uuid('entry_uuid');
$table->string('tag');
$table->primary(['entry_uuid', 'tag']);
$table->index('tag');
$table->foreign('entry_uuid')
->references('uuid')
->on('telescope_entries')
->onDelete('cascade');
});
$schema->create('telescope_monitoring', function (Blueprint $table) {
$table->string('tag')->primary();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
$schema = Schema::connection($this->getConnection());
$schema->dropIfExists('telescope_entries_tags');
$schema->dropIfExists('telescope_entries');
$schema->dropIfExists('telescope_monitoring');
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('local_file_volumes', function (Blueprint $table) {
$table->boolean('is_based_on_git')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('local_file_volumes', function (Blueprint $table) {
$table->dropColumn('is_based_on_git');
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->integer('order')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('environment_variables', function (Blueprint $table) {
$table->dropColumn('order');
});
}
};

View File

@@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('application_deployment_queues', function (Blueprint $table) {
$table->integer('build_server_id')->nullable()->after('server_id');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_deployment_queues', function (Blueprint $table) {
$table->dropColumn('build_server_id');
});
}
};

View File

@@ -0,0 +1,70 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('standalone_postgresqls', function (Blueprint $table) {
$table->text('custom_docker_run_options')->nullable();
});
Schema::table('standalone_mysqls', function (Blueprint $table) {
$table->text('custom_docker_run_options')->nullable();
});
Schema::table('standalone_mariadbs', function (Blueprint $table) {
$table->text('custom_docker_run_options')->nullable();
});
Schema::table('standalone_redis', function (Blueprint $table) {
$table->text('custom_docker_run_options')->nullable();
});
Schema::table('standalone_clickhouses', function (Blueprint $table) {
$table->text('custom_docker_run_options')->nullable();
});
Schema::table('standalone_dragonflies', function (Blueprint $table) {
$table->text('custom_docker_run_options')->nullable();
});
Schema::table('standalone_keydbs', function (Blueprint $table) {
$table->text('custom_docker_run_options')->nullable();
});
Schema::table('standalone_mongodbs', function (Blueprint $table) {
$table->text('custom_docker_run_options')->nullable();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('standalone_postgresqls', function (Blueprint $table) {
$table->dropColumn('custom_docker_run_options');
});
Schema::table('standalone_mysqls', function (Blueprint $table) {
$table->dropColumn('custom_docker_run_options');
});
Schema::table('standalone_mariadbs', function (Blueprint $table) {
$table->dropColumn('custom_docker_run_options');
});
Schema::table('standalone_redis', function (Blueprint $table) {
$table->dropColumn('custom_docker_run_options');
});
Schema::table('standalone_clickhouses', function (Blueprint $table) {
$table->dropColumn('custom_docker_run_options');
});
Schema::table('standalone_dragonflies', function (Blueprint $table) {
$table->dropColumn('custom_docker_run_options');
});
Schema::table('standalone_keydbs', function (Blueprint $table) {
$table->dropColumn('custom_docker_run_options');
});
Schema::table('standalone_mongodbs', function (Blueprint $table) {
$table->dropColumn('custom_docker_run_options');
});
}
};

View File

@@ -12,6 +12,7 @@ class StandaloneDockerSeeder extends Seeder
*/ */
public function run(): void public function run(): void
{ {
if (StandaloneDocker::find(0) == null) {
StandaloneDocker::create([ StandaloneDocker::create([
'id' => 0, 'id' => 0,
'name' => 'Standalone Docker 1', 'name' => 'Standalone Docker 1',
@@ -19,4 +20,5 @@ class StandaloneDockerSeeder extends Seeder
'server_id' => 0, 'server_id' => 0,
]); ]);
} }
}
} }

View File

@@ -247,13 +247,13 @@ paths:
security: security:
- -
bearerAuth: [] bearerAuth: []
/applications/private-gh-app: /applications/private-github-app:
post: post:
tags: tags:
- Applications - Applications
summary: 'Create (Private - GH App)' summary: 'Create (Private - GH App)'
description: 'Create new application based on a private repository through a Github App.' description: 'Create new application based on a private repository through a Github App.'
operationId: 4d46c84bda4f1a411f6dda15fce4061f operationId: 8b7af9c9a509385963bf3e72eeeea786
requestBody: requestBody:
description: 'Application object that needs to be created.' description: 'Application object that needs to be created.'
required: true required: true

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

21
public/svgs/budibase.svg Normal file
View File

@@ -0,0 +1,21 @@
<svg width="1200" height="265" viewBox="0 0 1200 265" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_1_17)">
<path d="M456.1 140.1C451.8 135.6 446.3 132.2 439.5 130C459.2 121.8 463.3 97.1 452.7 80.7C443.4 67.5 427.4 64.2 410.3 64C395 64 370.2 64 355.1 64V204C361.1 204 375.9 204 381.7 204C397.9 203.6 428.5 206 442.5 199.5C466.7 191.5 473.4 158 456.1 140.1ZM408.1 86.8C414.8 86.8 420.2 88 424.4 90.4C434.4 96.1 432.6 117.1 419.9 119.5C412.7 122.1 389.7 120.7 381.7 121V86.8H408.1ZM431.7 176.7C427.3 179.7 421.4 181.2 413.9 181.2H381.7V142.4H413.9C421.4 142.4 427.3 143.9 431.7 146.8C440.5 151.7 440.5 171.6 431.7 176.7Z" fill="black"/>
<path d="M527.6 206.3C502.3 207 481.5 188.6 482.4 162.5V101.1H507.6V161C507.3 175 514.6 183.8 527.6 184C540.6 183.8 547.8 175 547.6 161V101.2H572.6V162.6C573.5 188.5 552.7 207 527.6 206.3Z" fill="black"/>
<path d="M666.6 69.3V116.3C656.2 97.1 628.2 93.7 610.7 105.7C595.5 115.9 588.1 133.5 588.2 152.5C588.1 171.3 595.6 189.2 610.7 199.3C628.1 211.4 656.1 207.9 666.6 188.8V204H691.6V63L666.6 69.3ZM659.7 175.8C650.5 186.7 630.5 186.7 621.3 175.8C611.4 165.9 611.4 139.3 621.3 129.4C630.5 118.5 650.5 118.5 659.7 129.4C669.5 139.2 669.5 165.9 659.7 175.8Z" fill="black"/>
<path d="M730.9 88.8C709 89.3 709.1 58 730.9 58.4C752.4 58.3 752.5 89 730.9 88.8ZM743.1 101.2V204H718.1V101.2H743.1Z" fill="black"/>
<path d="M867 124.9C854.6 96.2 811.3 88 794.6 116.4V63L769.6 69.3V204H794.6V188.9C805 208 833.3 211.5 850.6 199.4C873.6 184.5 878.7 148.4 867 124.9ZM840 175.8C830.8 186.7 810.8 186.7 801.6 175.8C791.7 165.9 791.7 139.3 801.6 129.4C810.8 118.5 830.8 118.5 840 129.4C849.9 139.2 849.9 165.9 840 175.8Z" fill="black"/>
<path d="M973.4 116.4C954.1 90.2 902.3 92.8 890.1 125.1L911.2 134C917.7 118.9 943.3 115.9 952.1 130C955.5 134.8 954.8 145.2 954.8 151.4C935.4 133 885 141.8 886 174.3C884.7 208.5 938.3 216.1 956.4 194V203.9H979.8V139.3C979.8 130 977.6 122.4 973.4 116.4ZM949.2 184.3C941.7 189.3 925.4 189.2 917.9 184.4C909.8 180.1 909.8 167.9 917.9 163.6C928.4 157.6 954.7 157.6 955.2 174C955.2 178.4 953.2 181.8 949.2 184.3Z" fill="black"/>
<path d="M1043.7 206.3C1022.2 206.7 1001.5 194.6 996.9 172.3L1017.1 165.7C1019.8 179.2 1030.7 185.9 1043.7 185.9C1052.2 185.7 1062.1 184.1 1062.9 175.7C1062.2 166.9 1053.4 166.5 1044.3 163.5C1032.2 161.1 1014.7 155.9 1008.5 149C996.7 138.1 998 116.4 1011.7 107.1C1034.3 90.3 1082.5 98.3 1085.7 131.9L1064.9 137.3C1062.8 124.5 1053.5 119.2 1041.5 119.1C1033.7 119.2 1024.9 121.1 1024.3 128.9C1025.3 138.1 1036.6 138.2 1044.3 140.9C1056 143.2 1072.3 147.9 1078.8 155.1C1088.6 164 1089.5 180.8 1081.5 191.1C1072.9 202.3 1058.6 206.3 1043.7 206.3Z" fill="black"/>
<path d="M1193.8 124.2C1182.3 99.2 1148.3 91.9 1125.7 105.5C1109.3 115.3 1101.5 132.8 1101.6 152.6C1101.5 172.3 1109.6 190.1 1126.2 199.7C1150.3 213.7 1187 205.7 1198 177.9L1176.5 170.2C1170 184.2 1151.7 188.7 1138.8 181.4C1131.3 176.8 1127.8 167.6 1127 158.2H1199.7C1200.7 146.3 1198.7 133.5 1193.8 124.2ZM1151.6 119C1164.3 119 1172.5 128.2 1174.5 140.8H1127.8C1130 127.9 1138 118.9 1151.6 119Z" fill="black"/>
<path d="M158.2 8.6V116.6C158.2 121.3 162 125.2 166.8 125.2H213.8C218 125.2 222 123.2 224.6 119.8L262.9 68.9C265.7 65.2 265.7 60.1 262.9 56.4L224.6 5.4C222 2 218 0 213.8 0H166.8C162 0 158.2 3.8 158.2 8.6Z" fill="#FF4E4E"/>
<path d="M158.2 148.4V256.4C158.2 261.1 162 265 166.8 265H213.8C218 265 222 263 224.6 259.6L262.9 208.7C265.7 205 265.7 199.9 262.9 196.2L224.6 145.3C222.1 141.9 218.1 139.9 213.8 139.9H166.8C162 139.8 158.2 143.7 158.2 148.4Z" fill="#6E56FF"/>
<path d="M0 8.6V116.6C0 121.3 3.8 125.2 8.6 125.2H109.6C113.8 125.2 117.8 123.2 120.4 119.8L155.9 72.5C160.3 66.6 160.3 58.5 155.9 52.6L120.3 5.4C117.8 2 113.8 0 109.5 0H8.6C3.8 0 0 3.8 0 8.6Z" fill="#F97777"/>
<path d="M0 148.4V256.4C0 261.1 3.8 265 8.6 265H109.6C113.8 265 117.8 263 120.4 259.6L155.9 212.3C160.3 206.4 160.3 198.3 155.9 192.4L120.4 145.1C117.9 141.7 113.9 139.7 109.6 139.7H8.6C3.8 139.8 0 143.7 0 148.4Z" fill="#9F8FFF"/>
</g>
<defs>
<clipPath id="clip0_1_17">
<rect width="1200" height="265" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

BIN
public/svgs/chaskiq.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

1
public/svgs/rabbitmq.svg Normal file
View File

@@ -0,0 +1 @@
<svg height="271" preserveAspectRatio="xMidYMid" viewBox="0 0 256 271" width="256" xmlns="http://www.w3.org/2000/svg"><path d="m245.44 108.307692h-85.090462c-4.268307 0-7.734153-3.465846-7.734153-7.734154v-88.6793842c0-6.56738457-5.32677-11.8941538-11.889231-11.8941538h-30.375385c-6.567384 0-11.8892305 5.32676923-11.8892305 11.8941538v88.1427692c0 4.573539-3.6972308 8.290462-8.2707693 8.310154l-27.8843077.132923c-4.612923.024615-8.3593846-3.716923-8.3495384-8.324923l.1723077-88.2412308c.0147692-6.57723082-5.312-11.9138462-11.8892308-11.9138462h-30.3507692c-6.56738465 0-11.8892308 5.32676923-11.8892308 11.8941538v248.3150772c0 5.833846 4.72615385 10.56 10.5550769 10.56h234.8849231c5.833846 0 10.56-4.726154 10.56-10.56v-141.341539c0-5.833846-4.726154-10.56-10.56-10.56zm-39.901538 93.233231c0 7.645539-6.198154 13.843692-13.843693 13.843692h-24.004923c-7.645538 0-13.843692-6.198153-13.843692-13.843692v-24.004923c0-7.645538 6.198154-13.843692 13.843692-13.843692h24.004923c7.645539 0 13.843693 6.198154 13.843693 13.843692z" fill="#f60"/></svg>

After

Width:  |  Height:  |  Size: 1.0 KiB

29
public/svgs/windmill.svg Normal file
View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 256 256" style="enable-background:new 0 0 256 256;" xml:space="preserve">
<style type="text/css">
.st0{fill:#FFFFFF;}
.st1{opacity:0.4;fill:#FFFFFF;}
.st2{fill:#BCD4FC;}
.st3{fill:#3B82F6;}
.st4{fill:#B3B3B3;}
.st5{fill:url(#SVGID_1_);}
.st6{fill:url(#SVGID_00000021089067129159788970000008246765442136188072_);}
.st7{fill:url(#SVGID_00000117639240116366130650000015074833605515028638_);}
.st8{opacity:0.4;fill:url(#SVGID_00000101781798616409025840000016567063639337360777_);}
.st9{opacity:0.4;fill:url(#SVGID_00000052086836598721292040000002033117744178971046_);}
.st10{opacity:0.4;fill:url(#SVGID_00000159460939004760751800000002448009281983951536_);}
.st11{opacity:0.4;fill:url(#SVGID_00000013177830667419993080000017721442101626521532_);}
.st12{opacity:0.4;fill:url(#SVGID_00000152235521444854938490000006526001119318383285_);}
.st13{opacity:0.4;fill:url(#SVGID_00000119823135212293698520000012774889010992664993_);}
</style>
<g>
<polygon class="st2" points="134.78,14.22 114.31,48.21 101.33,69.75 158.22,69.75 177.97,36.95 191.67,14.22 "/>
<polygon class="st3" points="227.55,69.75 186.61,69.75 101.33,69.75 129.78,119.02 158.16,119.02 228.61,119.02 256,119.02 "/>
<polygon class="st3" points="136.93,132.47 116.46,167.93 73.82,241.78 130.71,241.78 144.9,217.2 180.13,156.18 193.82,132.46
"/>
<polygon class="st3" points="121.7,131.95 101.23,96.49 58.59,22.63 30.15,71.91 44.34,96.49 79.57,157.5 93.26,181.22 "/>
<polygon class="st2" points="64.81,131.95 25.15,131.21 0,130.74 28.44,180.01 66.73,180.72 93.26,181.21 "/>
<polygon class="st2" points="165.38,181.74 184.58,216.46 196.75,238.47 225.19,189.2 206.66,155.69 193.83,132.46 "/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

8
public/vendor/telescope/app-dark.css vendored Normal file

File diff suppressed because one or more lines are too long

7
public/vendor/telescope/app.css vendored Normal file

File diff suppressed because one or more lines are too long

2
public/vendor/telescope/app.js vendored Normal file

File diff suppressed because one or more lines are too long

BIN
public/vendor/telescope/favicon.ico vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

View File

@@ -0,0 +1,5 @@
{
"/app.js": "/app.js?id=48ba33a2532e4b6ec718bc0f03b3f1e4",
"/app-dark.css": "/app-dark.css?id=1ea407db56c5163ae29311f1f38eb7b9",
"/app.css": "/app.css?id=de4c978567bfd90b38d186937dee5ccf"
}

View File

@@ -28,16 +28,16 @@
<div class="flex flex-col justify-center flex-1"> <div class="flex flex-col justify-center flex-1">
<div class="box-title">{{ $project->name }}</div> <div class="box-title">{{ $project->name }}</div>
<div class="box-description"> <div class="box-description">
{{ $project->description }}</div> {{ $project->description }}
</div> </div>
<div class="flex items-center justify-center gap-2 text-xs font-bold "> </div>
<div class="flex items-center justify-center gap-2 text-xs font-bold">
<a class="hover:underline" <a class="hover:underline"
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}"> href="{{ route('project.resource.create', ['project_uuid' => $project->uuid, 'environment_name' => data_get($project, 'default_environment()', 'production')]) }}">
<span class="p-2 font-bold">+ <span class="p-2 font-bold">+ Add Resource</span>
Add Resource</span>
</a> </a>
<a class="hover:underline" <a class="hover:underline"
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}"> href="{{ route('project.edit', ['project_uuid' => $project->uuid]) }}">
Settings Settings
</a> </a>
</div> </div>
@@ -159,12 +159,14 @@
</div> </div>
@endif @endif
<script> <script>
function gotoProject(uuid, environment) { function gotoProject(uuid, environment) {
if (!environment) { if (environment) {
window.location.href = '/project/' + uuid + '/' + environment;
} else {
window.location.href = '/project/' + uuid; window.location.href = '/project/' + uuid;
} }
window.location.href = '/project/' + uuid + '/' + environment;
} }
</script> </script>
{{-- <x-forms.button wire:click='getIptables'>Get IPTABLES</x-forms.button> --}} {{-- <x-forms.button wire:click='getIptables'>Get IPTABLES</x-forms.button> --}}

View File

@@ -254,6 +254,11 @@
helper="You need to modify the docker compose file." monacoEditorLanguage="yaml" helper="You need to modify the docker compose file." monacoEditorLanguage="yaml"
useMonacoEditor /> useMonacoEditor />
@else @else
@if ($application->compose_parsing_version === '3')
<x-forms.textarea rows="10" readonly id="application.docker_compose_raw"
label="Docker Compose Content (raw)" helper="You need to modify the docker compose file."
monacoEditorLanguage="yaml" useMonacoEditor />
@endif
<x-forms.textarea rows="10" readonly id="application.docker_compose" <x-forms.textarea rows="10" readonly id="application.docker_compose"
label="Docker Compose Content" helper="You need to modify the docker compose file." label="Docker Compose Content" helper="You need to modify the docker compose file."
monacoEditorLanguage="yaml" useMonacoEditor /> monacoEditorLanguage="yaml" useMonacoEditor />

View File

@@ -21,14 +21,18 @@
readonly helper="You can only change this in the database." /> readonly helper="You can only change this in the database." />
</div> </div>
@else @else
<div class="pt-8 dark:text-warning">Please verify these values. You can only modify them before the initial <div class=" dark:text-warning">Please verify these values. You can only modify them before the initial
start. After that, you need to modify it in the database. start. After that, you need to modify it in the database.
</div> </div>
<div class="flex gap-2 pb-8"> <div class="flex gap-2">
<x-forms.input label="Username" id="database.clickhouse_admin_user" required /> <x-forms.input label="Username" id="database.clickhouse_admin_user" required />
<x-forms.input label="Password" id="database.clickhouse_admin_password" type="password" required /> <x-forms.input label="Password" id="database.clickhouse_admin_password" type="password" required />
</div> </div>
@endif @endif
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" />
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3> <h3 class="py-2">Network</h3>
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">

View File

@@ -11,6 +11,10 @@
<x-forms.input label="Description" id="database.description" /> <x-forms.input label="Description" id="database.description" />
<x-forms.input label="Image" id="database.image" required /> <x-forms.input label="Image" id="database.image" required />
</div> </div>
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" />
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3> <h3 class="py-2">Network</h3>
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">

View File

@@ -12,6 +12,10 @@
<x-forms.input label="Image" id="database.image" required <x-forms.input label="Image" id="database.image" required
helper="For all available images, check here:<br><br><a target='_blank' href=https://hub.docker.com/r/eqalpha/keydb'>https://hub.docker.com/r/eqalpha/keydb</a>" /> helper="For all available images, check here:<br><br><a target='_blank' href=https://hub.docker.com/r/eqalpha/keydb'>https://hub.docker.com/r/eqalpha/keydb</a>" />
</div> </div>
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" />
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3> <h3 class="py-2">Network</h3>
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">

View File

@@ -16,30 +16,40 @@
automations (like backups) won't work. automations (like backups) won't work.
</div> </div>
@if ($database->started_at) @if ($database->started_at)
<div class="flex flex-col gap-2"> <div class="flex xl:flex-row flex-col gap-2">
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password" required <x-forms.input label="Root Password" id="database.mariadb_root_password" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." /> helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Normal User" id="database.mariadb_user" required <x-forms.input label="Normal User" id="database.mariadb_user" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." /> helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required <x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." /> helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
</div>
<div class="flex flex-col gap-2">
<x-forms.input label="Initial Database" id="database.mariadb_database" <x-forms.input label="Initial Database" id="database.mariadb_database"
placeholder="If empty, it will be the same as Username." readonly placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." /> helper="You can only change this in the database." />
</div> </div>
@else @else
<div class="flex flex-col gap-2 pb-2"> <div class="flex xl:flex-row flex-col gap-2 pb-2">
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password" <x-forms.input label="Root Password" id="database.mariadb_root_password" type="password"
helper="You can only change this in the database." /> helper="You can only change this in the database." />
<x-forms.input label="Normal User" id="database.mariadb_user" required <x-forms.input label="Normal User" id="database.mariadb_user" required
helper="You can only change this in the database." /> helper="You can only change this in the database." />
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required <x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
helper="You can only change this in the database." /> helper="You can only change this in the database." />
</div>
<div class="flex flex-col gap-2">
<x-forms.input label="Initial Database" id="database.mariadb_database" <x-forms.input label="Initial Database" id="database.mariadb_database"
placeholder="If empty, it will be the same as Username." placeholder="If empty, it will be the same as Username."
helper="You can only change this in the database." /> helper="You can only change this in the database." />
</div> </div>
@endif @endif
<div class="pt-2">
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" />
</div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3> <h3 class="py-2">Network</h3>
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">

View File

@@ -16,7 +16,7 @@
automations (like backups) won't work. automations (like backups) won't work.
</div> </div>
@if ($database->started_at) @if ($database->started_at)
<div class="flex flex-col gap-2"> <div class="flex xl:flex-row flex-col gap-2">
<x-forms.input label="Initial Username" id="database.mongo_initdb_root_username" <x-forms.input label="Initial Username" id="database.mongo_initdb_root_username"
placeholder="If empty: postgres" placeholder="If empty: postgres"
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." /> helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
@@ -28,7 +28,7 @@
helper="You can only change this in the database." /> helper="You can only change this in the database." />
</div> </div>
@else @else
<div class="flex flex-col gap-2 pb-2"> <div class="flex xl:flex-row flex-col gap-2 pb-2">
<x-forms.input required label="Username" id="database.mongo_initdb_root_username" <x-forms.input required label="Username" id="database.mongo_initdb_root_username"
placeholder="If empty: postgres" /> placeholder="If empty: postgres" />
<x-forms.input label="Password" id="database.mongo_initdb_root_password" type="password" required /> <x-forms.input label="Password" id="database.mongo_initdb_root_password" type="password" required />
@@ -36,6 +36,10 @@
placeholder="If empty, it will be the same as Username." /> placeholder="If empty, it will be the same as Username." />
</div> </div>
@endif @endif
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" />
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3> <h3 class="py-2">Network</h3>
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">

View File

@@ -16,30 +16,40 @@
automations (like backups) won't work. automations (like backups) won't work.
</div> </div>
@if ($database->started_at) @if ($database->started_at)
<div class="flex flex-col gap-2"> <div class="flex xl:flex-row flex-col gap-2">
<x-forms.input label="Root Password" id="database.mysql_root_password" type="password" required <x-forms.input label="Root Password" id="database.mysql_root_password" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." /> helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Normal User" id="database.mysql_user" required <x-forms.input label="Normal User" id="database.mysql_user" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." /> helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required <x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." /> helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
</div>
<div class="flex flex-col gap-2">
<x-forms.input label="Initial Database" id="database.mysql_database" <x-forms.input label="Initial Database" id="database.mysql_database"
placeholder="If empty, it will be the same as Username." readonly placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." /> helper="You can only change this in the database." />
</div> </div>
@else @else
<div class="flex flex-col gap-4 pb-2"> <div class="flex xl:flex-row flex-col gap-4 pb-2">
<x-forms.input label="Root Password" id="database.mysql_root_password" type="password" <x-forms.input label="Root Password" id="database.mysql_root_password" type="password"
helper="You can only change this in the database." /> helper="You can only change this in the database." />
<x-forms.input label="Normal User" id="database.mysql_user" required <x-forms.input label="Normal User" id="database.mysql_user" required
helper="You can only change this in the database." /> helper="You can only change this in the database." />
<x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required <x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required
helper="You can only change this in the database." /> helper="You can only change this in the database." />
</div>
<div class="flex flex-col gap-2">
<x-forms.input label="Initial Database" id="database.mysql_database" <x-forms.input label="Initial Database" id="database.mysql_database"
placeholder="If empty, it will be the same as Username." placeholder="If empty, it will be the same as Username."
helper="You can only change this in the database." /> helper="You can only change this in the database." />
</div> </div>
@endif @endif
<div class="pt-2">
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" />
</div>
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3> <h3 class="py-2">Network</h3>
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">

View File

@@ -30,7 +30,7 @@
automations (like backups) won't work. automations (like backups) won't work.
</div> </div>
@if ($database->started_at) @if ($database->started_at)
<div class="flex flex-col gap-2"> <div class="flex xl:flex-row flex-col gap-2">
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres" <x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres"
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." /> helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Password" id="database.postgres_password" type="password" required <x-forms.input label="Password" id="database.postgres_password" type="password" required
@@ -40,7 +40,7 @@
helper="You can only change this in the database." /> helper="You can only change this in the database." />
</div> </div>
@else @else
<div class="flex flex-col gap-2 pb-2"> <div class="flex xl:flex-row flex-col gap-2 pb-2">
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres" /> <x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres" />
<x-forms.input label="Password" id="database.postgres_password" type="password" required /> <x-forms.input label="Password" id="database.postgres_password" type="password" required />
<x-forms.input label="Initial Database" id="database.postgres_db" <x-forms.input label="Initial Database" id="database.postgres_db"
@@ -53,6 +53,10 @@
<x-forms.input label="Host Auth Method" id="database.postgres_host_auth_method" <x-forms.input label="Host Auth Method" id="database.postgres_host_auth_method"
placeholder="If empty, use default. See in docker docs." /> placeholder="If empty, use default. See in docker docs." />
</div> </div>
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" />
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3> <h3 class="py-2">Network</h3>
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">

View File

@@ -12,6 +12,10 @@
<x-forms.input label="Image" id="database.image" required <x-forms.input label="Image" id="database.image" required
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/redis'>https://hub.docker.com/_/redis</a>" /> helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/redis'>https://hub.docker.com/_/redis</a>" />
</div> </div>
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" />
<div class="flex flex-col gap-2"> <div class="flex flex-col gap-2">
<h3 class="py-2">Network</h3> <h3 class="py-2">Network</h3>
<div class="flex items-end gap-2"> <div class="flex items-end gap-2">

View File

@@ -11,9 +11,8 @@
<div class="subtitle">All your projects are here.</div> <div class="subtitle">All your projects are here.</div>
<div class="grid gap-2 lg:grid-cols-2"> <div class="grid gap-2 lg:grid-cols-2">
@forelse ($projects as $project) @forelse ($projects as $project)
<div class="box group" x-data x-on:click="goto('{{ $project->uuid }}')"> <div class="box group" onclick="gotoProject('{{ $project->uuid }}', '{{ $project->default_environment() }}')">
<div class="flex flex-col justify-center flex-1 mx-6" <div class="flex flex-col justify-center flex-1 mx-6">
onclick="gotoProject('{{ $project->uuid }}','{{ $project->default_environment() }}')">
<div class="box-title">{{ $project->name }}</div> <div class="box-title">{{ $project->name }}</div>
<div class="box-description "> <div class="box-description ">
{{ $project->description }}</div> {{ $project->description }}</div>
@@ -33,15 +32,12 @@
</div> </div>
<script> <script>
function goto(uuid) {
window.location.href = '/project/' + uuid;
}
function gotoProject(uuid, environment) { function gotoProject(uuid, environment) {
if (!environment) { if (environment) {
window.location.href = '/project/' + uuid + '/' + environment;
} else {
window.location.href = '/project/' + uuid; window.location.href = '/project/' + uuid;
} }
window.location.href = '/project/' + uuid + '/' + environment;
} }
</script> </script>
</div> </div>

View File

@@ -617,6 +617,85 @@
@endif @endif
</div> </div>
@endif @endif
@if ($current_step === 'select-postgresql-type')
<h2>Select a Postgresql type</h2>
<div>If you need extra extensions, you can select Supabase PostgreSQL (or others), otherwise select PostgreSQL
16 (default).</div>
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<div class="gap-2 border border-transparent cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
wire:click="setPostgresqlType('postgres:16-alpine')">
<div class="flex flex-col">
<div class="box-title">PostgreSQL 16 (default)</div>
<div class="box-description">
PostgreSQL is a powerful, open-source object-relational database system (no extensions).
</div>
</div>
<div class="flex-1"></div>
<div class="flex items-center px-2" title="Read the documentation.">
<a class="p-2 hover:underline group-hover:dark:text-white dark:text-white text-neutral-6000"
onclick="event.stopPropagation()" href="https://hub.docker.com/_/postgres/"
target="_blank">
Documentation
</a>
</div>
</div>
<div class="gap-2 border border-transparent cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
wire:click="setPostgresqlType('supabase/postgres:15.6.1.113')">
<div class="flex flex-col">
<div class="box-title">Supabase PostgreSQL (with extensions)</div>
<div class="box-description">
Supabase is a modern, open-source alternative to PostgreSQL with lots of extensions.
</div>
</div>
<div class="flex-1"></div>
<div class="flex items-center px-2" title="Read the documentation.">
<a class="p-2 hover:underline group-hover:dark:text-white dark:text-white text-neutral-600"
onclick="event.stopPropagation()" href="https://github.com/supabase/postgres"
target="_blank">
Documentation
</a>
</div>
</div>
<div class="gap-2 border border-transparent cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
wire:click="setPostgresqlType('postgis/postgis')">
<div class="flex flex-col">
<div class="box-title">PostGIS</div>
<div class="box-description">
PostGIS is a PostgreSQL extension for geographic objects.
</div>
</div>
<div class="flex-1"></div>
<div class="flex items-center px-2" title="Read the documentation.">
<a class="p-2 hover:underline group-hover:dark:text-white dark:text-white text-neutral-600"
onclick="event.stopPropagation()" href="https://github.com/postgis/docker-postgis"
target="_blank">
Documentation
</a>
</div>
</div>
<div class="gap-2 border border-transparent cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
wire:click="setPostgresqlType('pgvector/pgvector:pg16')">
<div class="flex flex-col">
<div class="box-title">PGVector (16)</div>
<div class="box-description">
PGVector is a PostgreSQL extension for vector data types.
</div>
</div>
<div class="flex-1"></div>
<div class="flex items-center px-2" title="Read the documentation.">
<a class="p-2 hover:underline group-hover:dark:text-white dark:text-white text-neutral-600"
onclick="event.stopPropagation()" href="https://github.com/pgvector/pgvector"
target="_blank">
Documentation
</a>
</div>
</div>
</div>
</div>
@endif
@if ($current_step === 'existing-postgresql') @if ($current_step === 'existing-postgresql')
<form wire:submit='addExistingPostgresql' class="flex items-end gap-4"> <form wire:submit='addExistingPostgresql' class="flex items-end gap-4">
<x-forms.input placeholder="postgres://username:password@database:5432" label="Database URL" <x-forms.input placeholder="postgres://username:password@database:5432" label="Database URL"

View File

@@ -1,14 +1,16 @@
<div class="p-4 transition border rounded dark:border-coolgray-200"> <div class="py-4 ">
<div class="flex flex-col justify-center pb-4 text-sm select-text"> <div class="flex flex-col justify-center pb-4 text-sm select-text">
@if (data_get($resource, 'build_pack') === 'dockercompose') {{-- @if (data_get($resource, 'build_pack') === 'dockercompose')
<h2>{{ data_get($resource, 'name', 'unknown') }}</h2> <h4>{{ data_get($resource, 'name', 'unknown') }}</h4>
@endif @endif --}}
@if ($fileStorage->is_directory) @if ($fileStorage->is_directory)
<div class="dark:text-white">Directory Mount</div> <h4 class="dark:text-white">Directory Mount</h4>
@else @else
<div class="dark:text-white">File Mount</div> <h4 class="dark:text-white">File Mount</h4>
@endif @endif
<div>{{ $workdir }}{{ $fs_path }} -> {{ $fileStorage->mount_path }}</div>
<x-forms.input label="Source Path" :value="$fileStorage->fs_path" readonly />
<x-forms.input label="Destination Path" :value="$fileStorage->mount_path" readonly />
</div> </div>
<form wire:submit='submit' class="flex flex-col gap-2"> <form wire:submit='submit' class="flex flex-col gap-2">
<div class="flex gap-2"> <div class="flex gap-2">
@@ -26,6 +28,8 @@
</div> </div>
</x-modal-confirmation> </x-modal-confirmation>
@endif @endif
@if (!$fileStorage->is_based_on_git)
<x-modal-confirmation isErrorButton buttonTitle="Delete"> <x-modal-confirmation isErrorButton buttonTitle="Delete">
<div class="px-2">This storage will be deleted. It is not reversible. <strong <div class="px-2">This storage will be deleted. It is not reversible. <strong
class="text-error">Please class="text-error">Please
@@ -40,11 +44,23 @@
label="Permanently delete file from the server?"></x-forms.checkbox> label="Permanently delete file from the server?"></x-forms.checkbox>
@endif @endif
</x-modal-confirmation> </x-modal-confirmation>
@endif
</div> </div>
@if (!$fileStorage->is_directory) @if (!$fileStorage->is_directory)
<x-forms.textarea label="Content" rows="20" id="fileStorage.content"></x-forms.textarea> @if (data_get($resource, 'settings.is_preserve_repository_enabled'))
<div class="w-96">
<x-forms.checkbox instantSave label="Is this based on the Git repository?"
id="fileStorage.is_based_on_git"></x-forms.checkbox>
</div>
@endif
<x-forms.textarea
label="{{ $fileStorage->is_based_on_git ? 'Content (refreshed after a successful deployment)' : 'Content' }}"
rows="20" id="fileStorage.content"
readonly="{{ $fileStorage->is_based_on_git }}"></x-forms.textarea>
@if (!$fileStorage->is_based_on_git)
<x-forms.button class="w-full" type="submit">Save</x-forms.button> <x-forms.button class="w-full" type="submit">Save</x-forms.button>
@endif @endif
@endif
</form> </form>
</div> </div>

View File

@@ -31,10 +31,13 @@
@endif @endif
@if ($resource->persistentStorages()->get()->count() > 0) @if ($resource->persistentStorages()->get()->count() > 0)
<h3 class="pt-4">Volumes</h3>
<livewire:project.shared.storages.all :resource="$resource" /> <livewire:project.shared.storages.all :resource="$resource" />
@endif @endif
@if ($fileStorage->count() > 0) @if ($fileStorage->count() > 0)
<div class="flex flex-col gap-4 pt-4">
<h3 class="mt-4 pt-2 border-t dark:border-coolgray-200">Mounts</h3>
<div class="flex flex-col gap-2">
@foreach ($fileStorage->sort() as $fileStorage) @foreach ($fileStorage->sort() as $fileStorage)
<livewire:project.service.file-storage :fileStorage="$fileStorage" <livewire:project.service.file-storage :fileStorage="$fileStorage"
wire:key="resource-{{ $fileStorage->uuid }}" /> wire:key="resource-{{ $fileStorage->uuid }}" />

View File

@@ -19,9 +19,6 @@
<div class="box-description"> <div class="box-description">
Network: {{ data_get($resource, 'destination.network') }} Network: {{ data_get($resource, 'destination.network') }}
</div> </div>
@if ($resource->server_status == false)
<div class="text-xs font-bold text-error"> This server has connection problems. </div>
@endif
</div> </div>
@if ($resource?->additional_networks?->count() > 0) @if ($resource?->additional_networks?->count() > 0)
<div class="flex gap-2"> <div class="flex gap-2">

View File

@@ -2,9 +2,11 @@
<div> <div>
<div class="flex items-center gap-2"> <div class="flex items-center gap-2">
<h2>Environment Variables</h2> <h2>Environment Variables</h2>
<div class="flex flex-col items-center">
<x-modal-input buttonTitle="+ Add" title="New Environment Variable"> <x-modal-input buttonTitle="+ Add" title="New Environment Variable">
<livewire:project.shared.environment-variable.add /> <livewire:project.shared.environment-variable.add />
</x-modal-input> </x-modal-input>
</div>
<x-forms.button <x-forms.button
wire:click='switch'>{{ $view === 'normal' ? 'Developer view' : 'Normal view' }}</x-forms.button> wire:click='switch'>{{ $view === 'normal' ? 'Developer view' : 'Normal view' }}</x-forms.button>
</div> </div>
@@ -12,7 +14,7 @@
@if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose') @if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose')
<div class="w-64 pt-2"> <div class="w-64 pt-2">
<x-forms.checkbox id="resource.settings.is_env_sorting_enabled" label="Sort alphabetically" <x-forms.checkbox id="resource.settings.is_env_sorting_enabled" label="Sort alphabetically"
helper="Turn this off if one environment is dependent on an other. It will be sorted by creation order." helper="Turn this off if one environment is dependent on an other. It will be sorted by creation order (like you pasted them or in the order you created them)."
instantSave></x-forms.checkbox> instantSave></x-forms.checkbox>
</div> </div>
@endif @endif
@@ -31,6 +33,10 @@
@endif @endif
</div> </div>
@if ($view === 'normal') @if ($view === 'normal')
<div>
<h3>Production Environment Variables</h3>
<div>Environment (secrets) variables for Production.</div>
</div>
@forelse ($resource->environment_variables as $env) @forelse ($resource->environment_variables as $env)
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}" <livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
:env="$env" :type="$resource->type()" /> :env="$env" :type="$resource->type()" />
@@ -39,7 +45,7 @@
@endforelse @endforelse
@if ($resource->type() === 'application' && $resource->environment_variables_preview->count() > 0 && $showPreview) @if ($resource->type() === 'application' && $resource->environment_variables_preview->count() > 0 && $showPreview)
<div> <div>
<h3>Preview Deployments</h3> <h3>Preview Deployments Environment Variables</h3>
<div>Environment (secrets) variables for Preview Deployments.</div> <div>Environment (secrets) variables for Preview Deployments.</div>
</div> </div>
@foreach ($resource->environment_variables_preview as $env) @foreach ($resource->environment_variables_preview as $env)
@@ -48,16 +54,15 @@
@endforeach @endforeach
@endif @endif
@else @else
<form wire:submit='saveVariables(false)' class="flex flex-col gap-2"> <form wire:submit.prevent='submit' class="flex flex-col gap-2">
<x-forms.textarea rows="10" class="whitespace-pre-wrap" id="variables"></x-forms.textarea> <x-forms.textarea rows="10" class="whitespace-pre-wrap" id="variables" wire:model="variables" label="Production Environment Variables"></x-forms.textarea>
<x-forms.button type="submit" class="btn btn-primary">Save</x-forms.button>
</form>
@if ($showPreview) @if ($showPreview)
<form wire:submit='saveVariables(true)' class="flex flex-col gap-2"> <x-forms.textarea rows="10" class="whitespace-pre-wrap" label="Preview Deployments Environment Variables"
<x-forms.textarea rows="10" class="whitespace-pre-wrap" label="Preview Environment Variables" id="variablesPreview" wire:model="variablesPreview"></x-forms.textarea>
id="variablesPreview"></x-forms.textarea>
<x-forms.button type="submit" class="btn btn-primary">Save</x-forms.button>
</form>
@endif @endif
<x-forms.button type="submit" class="btn btn-primary">Save All Environment Variables</x-forms.button>
</form>
@endif @endif
</div> </div>

View File

@@ -1,15 +1,6 @@
<div> <div>
<form wire:submit='submit' <form wire:submit='submit'
class="flex flex-col items-center gap-4 p-4 bg-white border lg:items-start dark:bg-base dark:border-coolgray-300"> class="flex flex-col items-center gap-4 p-4 bg-white border lg:items-start dark:bg-base dark:border-coolgray-300">
{{-- @if (!$env->isFoundInCompose && !$isSharedVariable)
<div class="flex items-center justify-center gap-2 dark:text-warning text-coollabs"> <svg
class="hidden w-4 h-4 dark:text-warning lg:block" viewBox="0 0 256 256"
xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16">
</path>
</svg>This variable is not found in the compose file, so it won't be used.</div>
@endif --}}
@if ($isLocked) @if ($isLocked)
<div class="flex flex-1 w-full gap-2"> <div class="flex flex-1 w-full gap-2">
<x-forms.input disabled id="env.key" /> <x-forms.input disabled id="env.key" />

View File

@@ -33,7 +33,7 @@
screen.scrollTop = 0; screen.scrollTop = 0;
} }
}"> }">
<div class="flex items-center gap-2 "> <div class="flex gap-2 items-center">
@if ($resource?->type() === 'application' || str($resource?->type())->startsWith('standalone')) @if ($resource?->type() === 'application' || str($resource?->type())->startsWith('standalone'))
<h4>{{ $container }}</h4> <h4>{{ $container }}</h4>
@else @else
@@ -46,9 +46,9 @@
<x-loading wire:poll.2000ms='getLogs(true)' /> <x-loading wire:poll.2000ms='getLogs(true)' />
@endif @endif
</div> </div>
<form wire:submit='getLogs(true)' class="flex items-end gap-2 "> <form wire:submit='getLogs(true)' class="flex gap-2 items-end">
<div class="w-96"> <div class="w-96">
<x-forms.input label="Only Show Number of Lines" placeholder="1000" required <x-forms.input label="Only Show Number of Lines" placeholder="1000" type="number" required
id="numberOfLines"></x-forms.input> id="numberOfLines"></x-forms.input>
</div> </div>
<x-forms.button type="submit">Refresh</x-forms.button> <x-forms.button type="submit">Refresh</x-forms.button>
@@ -56,7 +56,7 @@
<x-forms.checkbox instantSave label="Include Timestamps" id="showTimeStamps"></x-forms.checkbox> <x-forms.checkbox instantSave label="Include Timestamps" id="showTimeStamps"></x-forms.checkbox>
</form> </form>
<div :class="fullscreen ? 'fullscreen' : 'relative w-full py-4 mx-auto'"> <div :class="fullscreen ? 'fullscreen' : 'relative w-full py-4 mx-auto'">
<div class="flex flex-col-reverse w-full px-4 py-2 overflow-y-auto bg-white dark:text-white dark:bg-coolgray-100 scrollbar dark:border-coolgray-300" <div class="flex overflow-y-auto flex-col-reverse px-4 py-2 w-full bg-white dark:text-white dark:bg-coolgray-100 scrollbar dark:border-coolgray-300"
:class="fullscreen ? '' : 'max-h-96 border border-solid rounded'"> :class="fullscreen ? '' : 'max-h-96 border border-solid rounded'">
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4" <button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4"
x-on:click="makeFullscreen"><svg class="icon" viewBox="0 0 24 24" x-on:click="makeFullscreen"><svg class="icon" viewBox="0 0 24 24"
@@ -76,7 +76,7 @@
stroke-width="2" d="M12 5v14m4-4l-4 4m-4-4l4 4" /> stroke-width="2" d="M12 5v14m4-4l-4 4m-4-4l4 4" />
</svg></button> </svg></button>
<button title="Fullscreen" x-show="!fullscreen" class="absolute top-5 right-1" <button title="Fullscreen" x-show="!fullscreen" class="absolute right-1 top-5"
x-on:click="makeFullscreen"><svg class="icon" viewBox="0 0 24 24" x-on:click="makeFullscreen"><svg class="icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"> xmlns="http://www.w3.org/2000/svg">
<g fill="none"> <g fill="none">

Some files were not shown because too many files have changed in this diff Show More