Merge branch 'next' into set-server-timzone-setting
@ -49,6 +49,8 @@ # Donations
|
||||
<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://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+)
|
||||
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
||||
@ -71,8 +73,10 @@ ## Github Sponsors ($40+)
|
||||
<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://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/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
|
||||
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
||||
|
@ -21,7 +21,7 @@ public function handle(StandaloneClickhouse $database)
|
||||
$this->database = $database;
|
||||
|
||||
$container_name = $this->database->uuid;
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
@ -75,7 +75,7 @@ public function handle(StandaloneClickhouse $database)
|
||||
],
|
||||
],
|
||||
];
|
||||
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);
|
||||
}
|
||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||
@ -102,6 +102,11 @@ public function handle(StandaloneClickhouse $database)
|
||||
if (count($volume_names) > 0) {
|
||||
$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_base64 = base64_encode($docker_compose);
|
||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||
@ -120,10 +125,10 @@ private function generate_local_persistent_volumes()
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
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 {
|
||||
$volume_name = $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,11 +159,11 @@ private function generate_environment_variables()
|
||||
$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}");
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ public function handle(StandaloneDragonfly $database)
|
||||
$startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}";
|
||||
|
||||
$container_name = $this->database->uuid;
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
@ -75,7 +75,7 @@ public function handle(StandaloneDragonfly $database)
|
||||
],
|
||||
],
|
||||
];
|
||||
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);
|
||||
}
|
||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||
@ -102,6 +102,11 @@ public function handle(StandaloneDragonfly $database)
|
||||
if (count($volume_names) > 0) {
|
||||
$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_base64 = base64_encode($docker_compose);
|
||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||
@ -120,10 +125,10 @@ private function generate_local_persistent_volumes()
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
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 {
|
||||
$volume_name = $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,7 +159,7 @@ private function generate_environment_variables()
|
||||
$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}");
|
||||
}
|
||||
|
||||
|
@ -24,7 +24,7 @@ public function handle(StandaloneKeydb $database)
|
||||
$startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes";
|
||||
|
||||
$container_name = $this->database->uuid;
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
@ -74,7 +74,7 @@ public function handle(StandaloneKeydb $database)
|
||||
],
|
||||
],
|
||||
];
|
||||
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);
|
||||
}
|
||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||
@ -101,15 +101,19 @@ public function handle(StandaloneKeydb $database)
|
||||
if (count($volume_names) > 0) {
|
||||
$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'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir.'/keydb.conf',
|
||||
'source' => $this->configuration_dir . '/keydb.conf',
|
||||
'target' => '/etc/keydb/keydb.conf',
|
||||
'read_only' => true,
|
||||
];
|
||||
$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_base64 = base64_encode($docker_compose);
|
||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||
@ -128,10 +132,10 @@ private function generate_local_persistent_volumes()
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
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 {
|
||||
$volume_name = $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
}
|
||||
|
||||
@ -162,7 +166,7 @@ private function generate_environment_variables()
|
||||
$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}");
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ public function handle(StandaloneMariadb $database)
|
||||
$this->database = $database;
|
||||
|
||||
$container_name = $this->database->uuid;
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
@ -69,7 +69,7 @@ public function handle(StandaloneMariadb $database)
|
||||
],
|
||||
],
|
||||
];
|
||||
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);
|
||||
}
|
||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||
@ -96,14 +96,19 @@ public function handle(StandaloneMariadb $database)
|
||||
if (count($volume_names) > 0) {
|
||||
$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'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir.'/custom-config.cnf',
|
||||
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||
'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_base64 = base64_encode($docker_compose);
|
||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||
@ -122,10 +127,10 @@ private function generate_local_persistent_volumes()
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
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 {
|
||||
$volume_name = $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,18 +161,18 @@ private function generate_environment_variables()
|
||||
$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}");
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
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}");
|
||||
}
|
||||
|
||||
|
@ -23,7 +23,7 @@ public function handle(StandaloneMongodb $database)
|
||||
$startCommand = 'mongod';
|
||||
|
||||
$container_name = $this->database->uuid;
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
@ -77,7 +77,7 @@ public function handle(StandaloneMongodb $database)
|
||||
],
|
||||
],
|
||||
];
|
||||
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);
|
||||
}
|
||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||
@ -104,23 +104,27 @@ public function handle(StandaloneMongodb $database)
|
||||
if (count($volume_names) > 0) {
|
||||
$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'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir.'/mongod.conf',
|
||||
'source' => $this->configuration_dir . '/mongod.conf',
|
||||
'target' => '/etc/mongo/mongod.conf',
|
||||
'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();
|
||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir.'/docker-entrypoint-initdb.d',
|
||||
'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d',
|
||||
'target' => '/docker-entrypoint-initdb.d',
|
||||
'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_base64 = base64_encode($docker_compose);
|
||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||
@ -139,10 +143,10 @@ private function generate_local_persistent_volumes()
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
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 {
|
||||
$volume_name = $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
}
|
||||
|
||||
@ -173,15 +177,15 @@ private function generate_environment_variables()
|
||||
$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}");
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ public function handle(StandaloneMysql $database)
|
||||
$this->database = $database;
|
||||
|
||||
$container_name = $this->database->uuid;
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
@ -69,7 +69,7 @@ public function handle(StandaloneMysql $database)
|
||||
],
|
||||
],
|
||||
];
|
||||
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);
|
||||
}
|
||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||
@ -96,14 +96,19 @@ public function handle(StandaloneMysql $database)
|
||||
if (count($volume_names) > 0) {
|
||||
$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'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir.'/custom-config.cnf',
|
||||
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||
'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_base64 = base64_encode($docker_compose);
|
||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||
@ -122,10 +127,10 @@ private function generate_local_persistent_volumes()
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
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 {
|
||||
$volume_name = $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
}
|
||||
|
||||
@ -156,18 +161,18 @@ private function generate_environment_variables()
|
||||
$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}");
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
|
||||
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}");
|
||||
}
|
||||
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}");
|
||||
}
|
||||
|
||||
|
@ -37,6 +37,7 @@ public function handle(StandalonePostgresql $database)
|
||||
$this->generate_init_scripts();
|
||||
$this->add_custom_conf();
|
||||
|
||||
|
||||
$docker_compose = [
|
||||
'services' => [
|
||||
$container_name => [
|
||||
@ -126,6 +127,10 @@ public function handle(StandalonePostgresql $database)
|
||||
'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_base64 = base64_encode($docker_compose);
|
||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||
|
@ -24,7 +24,7 @@ public function handle(StandaloneRedis $database)
|
||||
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
||||
|
||||
$container_name = $this->database->uuid;
|
||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
@ -78,7 +78,7 @@ public function handle(StandaloneRedis $database)
|
||||
],
|
||||
],
|
||||
];
|
||||
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);
|
||||
}
|
||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||
@ -105,15 +105,20 @@ public function handle(StandaloneRedis $database)
|
||||
if (count($volume_names) > 0) {
|
||||
$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'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir.'/redis.conf',
|
||||
'source' => $this->configuration_dir . '/redis.conf',
|
||||
'target' => '/usr/local/etc/redis/redis.conf',
|
||||
'read_only' => true,
|
||||
];
|
||||
$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_base64 = base64_encode($docker_compose);
|
||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||
@ -132,10 +137,10 @@ private function generate_local_persistent_volumes()
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
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 {
|
||||
$volume_name = $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,7 +171,7 @@ private function generate_environment_variables()
|
||||
$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}");
|
||||
}
|
||||
|
||||
|
@ -9,17 +9,24 @@ class CleanupDocker
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(Server $server, bool $force = true)
|
||||
public function handle(Server $server)
|
||||
{
|
||||
// cleanup docker images, containers, and builder caches
|
||||
if ($force) {
|
||||
instant_remote_process(['docker image prune -af'], $server, false);
|
||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
||||
instant_remote_process(['docker builder prune -af'], $server, false);
|
||||
} else {
|
||||
instant_remote_process(['docker image prune -f'], $server, false);
|
||||
instant_remote_process(['docker container prune -f --filter "label=coolify.managed=true"'], $server, false);
|
||||
instant_remote_process(['docker builder prune -f'], $server, false);
|
||||
|
||||
$commands = $this->getCommands();
|
||||
|
||||
foreach ($commands as $command) {
|
||||
instant_remote_process([$command], $server, false);
|
||||
}
|
||||
}
|
||||
|
||||
private function getCommands(): array
|
||||
{
|
||||
$commonCommands = [
|
||||
'docker container prune -f --filter "label=coolify.managed=true"',
|
||||
'docker image prune -af',
|
||||
'docker builder prune -af',
|
||||
];
|
||||
|
||||
return $commonCommands;
|
||||
}
|
||||
}
|
||||
|
@ -26,7 +26,7 @@ public function handle($manual_update = false)
|
||||
if (! $this->server) {
|
||||
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');
|
||||
if ($response->successful()) {
|
||||
$versions = $response->json();
|
||||
|
@ -132,6 +132,9 @@ private function cleanup_unnecessary_dynamic_proxy_configuration()
|
||||
|
||||
private function cleanup_unused_network_from_coolify_proxy()
|
||||
{
|
||||
if (isCloud()) {
|
||||
return;
|
||||
}
|
||||
foreach ($this->servers as $server) {
|
||||
if (! $server->isFunctional()) {
|
||||
continue;
|
||||
|
26
app/Console/Commands/OpenApi.php
Normal 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();
|
||||
|
||||
}
|
||||
}
|
@ -30,11 +30,11 @@ protected function schedule(Schedule $schedule): void
|
||||
$this->all_servers = Server::all();
|
||||
$settings = InstanceSettings::get();
|
||||
|
||||
$schedule->command('telescope:prune')->daily();
|
||||
if (isDev()) {
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyMinute();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyMinute()->onOneServer();
|
||||
|
||||
// Server Jobs
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->check_resources($schedule);
|
||||
@ -43,7 +43,7 @@ protected function schedule(Schedule $schedule): void
|
||||
} else {
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||
$schedule->command('cleanup:unreachable-servers')->daily();
|
||||
$schedule->command('cleanup:unreachable-servers')->daily()->onOneServer();
|
||||
$schedule->job(new PullCoolifyImageJob)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
||||
$schedule->job(new PullTemplatesFromCDN)->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
||||
$schedule->job(new CleanupInstanceStuffsJob)->everyTwoMinutes()->onOneServer();
|
||||
@ -66,10 +66,14 @@ private function pull_images($schedule)
|
||||
$servers = $this->all_servers->where('settings.is_usable', true)->where('settings.is_reachable', true)->where('ip', '!=', '1.2.3.4');
|
||||
foreach ($servers as $server) {
|
||||
if ($server->isSentinelEnabled()) {
|
||||
$schedule->job(new PullSentinelImageJob($server))
|
||||
->cron($settings->update_check_frequency)
|
||||
->timezone($settings->instance_timezone)
|
||||
->onOneServer();
|
||||
$schedule->job(function () use ($server) {
|
||||
$sentinel_found = instant_remote_process(['docker inspect coolify-sentinel'], $server, false);
|
||||
$sentinel_found = json_decode($sentinel_found, true);
|
||||
$status = data_get($sentinel_found, '0.State.Status', 'exited');
|
||||
if ($status !== 'running') {
|
||||
PullSentinelImageJob::dispatch($server);
|
||||
}
|
||||
})->cron($settings->update_check_frequency)->timezone($settings->instance_timezone)->onOneServer();
|
||||
}
|
||||
$schedule->job(new PullHelperImageJob($server))
|
||||
->cron($settings->update_check_frequency)
|
||||
@ -109,10 +113,11 @@ private function check_resources($schedule)
|
||||
foreach ($servers as $server) {
|
||||
$schedule->job(new ServerCheckJob($server))->everyMinute()->onOneServer();
|
||||
$serverTimezone = $server->settings->server_timezone;
|
||||
$schedule->job(new DockerCleanupJob($server))
|
||||
->everyTenMinutes() //will be changed in the second PR
|
||||
->timezone($serverTimezone)
|
||||
->onOneServer();
|
||||
if ($server->settings->force_docker_cleanup) {
|
||||
$schedule->job(new DockerCleanupJob($server))->cron($server->settings->docker_cleanup_frequency)->timezone($serverTimezone)->onOneServer();
|
||||
} else {
|
||||
$schedule->job(new DockerCleanupJob($server))->everyTenMinutes()->timezone($serverTimezone)->onOneServer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,7 +201,7 @@ public function create_public_application(Request $request)
|
||||
#[OA\Post(
|
||||
summary: 'Create (Private - GH 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: [
|
||||
['bearerAuth' => []],
|
||||
],
|
||||
@ -1450,7 +1450,7 @@ public function update_by_uuid(Request $request)
|
||||
], 404);
|
||||
}
|
||||
$server = $application->destination->server;
|
||||
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect'];
|
||||
$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(), [
|
||||
sharedDataApplications(),
|
||||
@ -1526,6 +1526,10 @@ public function update_by_uuid(Request $request)
|
||||
}
|
||||
$request->offsetUnset('docker_compose_domains');
|
||||
}
|
||||
$instantDeploy = $request->instant_deploy;
|
||||
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
|
||||
$data = $request->all();
|
||||
data_set($data, 'fqdn', $domains);
|
||||
if ($dockerComposeDomainsJson->count() > 0) {
|
||||
@ -1534,6 +1538,16 @@ public function update_by_uuid(Request $request)
|
||||
$application->fill($data);
|
||||
$application->save();
|
||||
|
||||
if ($instantDeploy) {
|
||||
$deployment_uuid = new Cuid2;
|
||||
|
||||
queue_application_deployment(
|
||||
application: $application,
|
||||
deployment_uuid: $deployment_uuid,
|
||||
is_api: true,
|
||||
);
|
||||
}
|
||||
|
||||
return response()->json([
|
||||
'uuid' => $application->uuid,
|
||||
]);
|
||||
|
@ -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.',
|
||||
]);
|
||||
}
|
||||
}
|
@ -157,7 +157,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
private ?string $coolify_variables = null;
|
||||
|
||||
private bool $preserveRepository = true;
|
||||
private bool $preserveRepository = false;
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
@ -198,9 +198,13 @@ public function __construct(int $application_deployment_queue_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) {
|
||||
$this->container_name = $this->application->settings->custom_internal_name;
|
||||
if ($this->pull_request_id === 0) {
|
||||
$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);
|
||||
$this->saved_outputs = collect();
|
||||
@ -276,6 +280,7 @@ public function handle(): void
|
||||
$this->original_server = $this->server;
|
||||
} else {
|
||||
$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->original_server = $this->server;
|
||||
$this->use_build_server = true;
|
||||
@ -414,15 +419,43 @@ private function deploy_docker_compose_buildpack()
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$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->cleanup_git();
|
||||
$this->application->loadComposeFile(isInit: false);
|
||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||
$this->application->parseRawCompose();
|
||||
$this->application->oldRawParser();
|
||||
$yaml = $composeFile = $this->application->docker_compose_raw;
|
||||
$this->save_environment_variables();
|
||||
} 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();
|
||||
if (! is_null($this->env_filename)) {
|
||||
$services = collect($composeFile['services']);
|
||||
@ -439,7 +472,7 @@ private function deploy_docker_compose_buildpack()
|
||||
|
||||
return;
|
||||
}
|
||||
$yaml = Yaml::dump($composeFile->toArray(), 10);
|
||||
$yaml = Yaml::dump(convertToArray($composeFile), 10);
|
||||
}
|
||||
$this->docker_compose_base64 = base64_encode($yaml);
|
||||
$this->execute_remote_command([
|
||||
@ -480,6 +513,7 @@ private function deploy_docker_compose_buildpack()
|
||||
}
|
||||
|
||||
// Start compose file
|
||||
$server_workdir = $this->application->workdir();
|
||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||
if ($this->docker_compose_custom_start_command) {
|
||||
$this->write_deployment_configurations();
|
||||
@ -488,7 +522,6 @@ private function deploy_docker_compose_buildpack()
|
||||
);
|
||||
} else {
|
||||
$this->write_deployment_configurations();
|
||||
$server_workdir = $this->application->workdir();
|
||||
$this->docker_compose_location = '/docker-compose.yaml';
|
||||
|
||||
$command = "{$this->coolify_variables} docker compose";
|
||||
@ -508,15 +541,27 @@ private function deploy_docker_compose_buildpack()
|
||||
);
|
||||
} else {
|
||||
$command = "{$this->coolify_variables} docker compose";
|
||||
if ($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";
|
||||
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) {
|
||||
$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";
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
||||
);
|
||||
$this->write_deployment_configurations();
|
||||
}
|
||||
|
||||
$this->write_deployment_configurations();
|
||||
$this->execute_remote_command(
|
||||
[executeInDocker($this->deployment_uuid, $command), 'hidden' => true],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -610,15 +655,16 @@ private function write_deployment_configurations()
|
||||
[
|
||||
"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}",
|
||||
],
|
||||
);
|
||||
}
|
||||
foreach ($this->application->fileStorages as $fileStorage) {
|
||||
if (! $fileStorage->is_based_on_git && ! $fileStorage->is_directory) {
|
||||
$fileStorage->saveStorageOnServer();
|
||||
}
|
||||
}
|
||||
if ($this->use_build_server) {
|
||||
$this->server = $this->build_server;
|
||||
}
|
||||
@ -851,11 +897,13 @@ private function save_environment_variables()
|
||||
$url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', '');
|
||||
$envs->push("COOLIFY_URL={$url}");
|
||||
}
|
||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
||||
}
|
||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
||||
if ($this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
||||
}
|
||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
||||
}
|
||||
}
|
||||
foreach ($sorted_environment_variables_preview as $env) {
|
||||
$real_value = $env->real_value;
|
||||
@ -897,11 +945,13 @@ private function save_environment_variables()
|
||||
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
|
||||
$envs->push("COOLIFY_URL={$url}");
|
||||
}
|
||||
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
||||
}
|
||||
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
||||
if ($this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
||||
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
||||
}
|
||||
if ($this->application->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
||||
}
|
||||
}
|
||||
foreach ($sorted_environment_variables as $env) {
|
||||
$real_value = $env->real_value;
|
||||
@ -1708,13 +1758,20 @@ private function generate_compose_file()
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
$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";
|
||||
})->toArray();
|
||||
})->toArray());
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
@ -2140,7 +2197,6 @@ private function add_build_env_variables_to_dockerfile()
|
||||
} else {
|
||||
$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"));
|
||||
|
@ -56,6 +56,8 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
|
||||
public ?string $backup_output = null;
|
||||
|
||||
public ?string $postgres_password = null;
|
||||
|
||||
public ?S3Storage $s3 = null;
|
||||
|
||||
public function __construct($backup)
|
||||
@ -89,8 +91,6 @@ public function uniqueId(): int
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
BackupCreated::dispatch($this->team->id);
|
||||
|
||||
// Check if team is exists
|
||||
if (is_null($this->team)) {
|
||||
$this->backup->update(['status' => 'failed']);
|
||||
@ -99,6 +99,9 @@ public function handle(): void
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
BackupCreated::dispatch($this->team->id);
|
||||
|
||||
$status = str(data_get($this->database, 'status'));
|
||||
if (! $status->startsWith('running') && $this->database->id !== 0) {
|
||||
ray('database not running');
|
||||
@ -134,6 +137,13 @@ public function handle(): void
|
||||
} else {
|
||||
$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')) {
|
||||
$this->container_name = "{$this->database->name}-$serviceUuid";
|
||||
$this->directory_name = $serviceName.'-'.$this->container_name;
|
||||
@ -336,7 +346,7 @@ private function backup_standalone_mongodb(string $databaseWithCollections): voi
|
||||
$url = $this->database->internal_db_url;
|
||||
if ($databaseWithCollections === 'all') {
|
||||
$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";
|
||||
} else {
|
||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
|
||||
@ -351,13 +361,13 @@ private function backup_standalone_mongodb(string $databaseWithCollections): voi
|
||||
}
|
||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||
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";
|
||||
} else {
|
||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location";
|
||||
}
|
||||
} 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";
|
||||
} else {
|
||||
$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 @@ private function backup_standalone_postgresql(string $database): void
|
||||
{
|
||||
try {
|
||||
$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 = trim($this->backup_output);
|
||||
if ($this->backup_output === '') {
|
||||
@ -452,7 +468,7 @@ private function remove_old_backups(): void
|
||||
if ($this->backup->number_of_backups_locally === 0) {
|
||||
$deletable = $this->backup->executions()->where('status', 'success');
|
||||
} else {
|
||||
$deletable = $this->backup->executions()->where('status', 'success')->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) {
|
||||
delete_backup_locally($execution->filename, $this->server);
|
||||
|
@ -10,6 +10,7 @@
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
@ -17,21 +18,33 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $timeout = 300;
|
||||
public $timeout = 600;
|
||||
|
||||
public int|string|null $usageBefore = null;
|
||||
public $tries = 1;
|
||||
|
||||
public ?string $usageBefore = null;
|
||||
|
||||
public function __construct(public Server $server) {}
|
||||
|
||||
public function middleware(): array
|
||||
{
|
||||
return [new WithoutOverlapping($this->server->id)];
|
||||
}
|
||||
|
||||
public function uniqueId(): int
|
||||
{
|
||||
return $this->server->id;
|
||||
}
|
||||
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
if (! $this->server->isFunctional()) {
|
||||
return;
|
||||
}
|
||||
if ($this->server->settings->is_force_cleanup_enabled) {
|
||||
if ($this->server->settings->force_docker_cleanup) {
|
||||
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
|
||||
CleanupDocker::run(server: $this->server, force: true);
|
||||
CleanupDocker::run(server: $this->server);
|
||||
|
||||
return;
|
||||
}
|
||||
@ -39,12 +52,12 @@ public function handle(): void
|
||||
$this->usageBefore = $this->server->getDiskUsage();
|
||||
if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
|
||||
Log::info('DockerCleanupJob force cleanup on '.$this->server->name);
|
||||
CleanupDocker::run(server: $this->server, force: true);
|
||||
CleanupDocker::run(server: $this->server);
|
||||
|
||||
return;
|
||||
}
|
||||
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
||||
CleanupDocker::run(server: $this->server, force: false);
|
||||
if ($this->usageBefore >= $this->server->settings->docker_cleanup_threshold) {
|
||||
CleanupDocker::run(server: $this->server);
|
||||
$usageAfter = $this->server->getDiskUsage();
|
||||
if ($usageAfter < $this->usageBefore) {
|
||||
$this->server->team?->notify(new DockerCleanup($this->server, 'Saved '.($this->usageBefore - $usageAfter).'% disk space.'));
|
||||
@ -56,7 +69,8 @@ public function handle(): void
|
||||
Log::info('No need to clean up '.$this->server->name);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
CleanupDocker::run(server: $this->server);
|
||||
Log::error('DockerCleanupJob failed: '.$e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,6 @@ public function handle()
|
||||
}
|
||||
GetContainersStatus::run($this->server, $this->containers, $containerReplicates);
|
||||
$this->checkLogDrainContainer();
|
||||
$this->checkSentinel();
|
||||
}
|
||||
|
||||
} catch (\Throwable $e) {
|
||||
@ -90,21 +89,6 @@ public function handle()
|
||||
|
||||
}
|
||||
|
||||
private function checkSentinel()
|
||||
{
|
||||
if ($this->server->isSentinelEnabled()) {
|
||||
$sentinelContainerFound = $this->containers->filter(function ($value, $key) {
|
||||
return data_get($value, 'Name') === '/coolify-sentinel';
|
||||
})->first();
|
||||
if ($sentinelContainerFound) {
|
||||
$status = data_get($sentinelContainerFound, 'State.Status');
|
||||
if ($status !== 'running') {
|
||||
PullSentinelImageJob::dispatch($this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function serverStatus()
|
||||
{
|
||||
['uptime' => $uptime] = $this->server->validateConnection();
|
||||
|
@ -66,9 +66,9 @@ public function instantSave()
|
||||
$this->dispatch('resetDefaultLabels', false);
|
||||
}
|
||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||
$this->application->parseRawCompose();
|
||||
$this->application->oldRawParser();
|
||||
} else {
|
||||
$this->application->parseCompose();
|
||||
$this->application->oldParser();
|
||||
}
|
||||
$this->application->settings->save();
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
@ -96,6 +96,12 @@ public function saveCustomName()
|
||||
} else {
|
||||
$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;
|
||||
$server = $this->application->destination->server;
|
||||
$allApplications = $server->applications();
|
||||
|
@ -55,9 +55,14 @@ public function force_start()
|
||||
public function cancel()
|
||||
{
|
||||
$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;
|
||||
try {
|
||||
$server = Server::find($server_id);
|
||||
if ($this->application->settings->is_build_server_enabled) {
|
||||
$server = Server::find($build_server_id);
|
||||
} else {
|
||||
$server = Server::find($server_id);
|
||||
}
|
||||
if ($this->application_deployment_queue->logs) {
|
||||
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||
|
||||
|
@ -3,8 +3,8 @@
|
||||
namespace App\Livewire\Project\Application;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\LocalFileVolume;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@ -30,6 +30,8 @@ class General extends Component
|
||||
|
||||
public ?string $ports_exposes = null;
|
||||
|
||||
public bool $is_preserve_repository_enabled = false;
|
||||
|
||||
public bool $is_container_label_escape_enabled = true;
|
||||
|
||||
public $customLabels;
|
||||
@ -130,7 +132,7 @@ class General extends Component
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
$this->parsedServices = $this->application->parseCompose();
|
||||
$this->parsedServices = $this->application->oldParser();
|
||||
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.');
|
||||
|
||||
@ -145,6 +147,7 @@ public function mount()
|
||||
}
|
||||
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
|
||||
$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->customLabels = $this->application->parseContainerLabels();
|
||||
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
|
||||
@ -168,9 +171,21 @@ public function instantSave()
|
||||
$this->application->settings->save();
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
$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) {
|
||||
$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)
|
||||
@ -179,39 +194,18 @@ public function loadComposeFile($isInit = false)
|
||||
if ($isInit && $this->application->docker_compose_raw) {
|
||||
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);
|
||||
if (is_null($this->parsedServices)) {
|
||||
$this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
|
||||
|
||||
return;
|
||||
}
|
||||
$compose = $this->application->parseCompose();
|
||||
$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->application->oldParser();
|
||||
$this->dispatch('success', 'Docker compose file loaded.');
|
||||
$this->dispatch('compose_loaded');
|
||||
$this->dispatch('refreshStorages');
|
||||
|
@ -57,7 +57,7 @@ public function download_file($exeuctionId)
|
||||
public function refreshBackupExecutions(): void
|
||||
{
|
||||
if ($this->backup) {
|
||||
$this->executions = $this->backup->executions()->get()->sortBy('created_at');
|
||||
$this->executions = $this->backup->executions()->get();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ class General extends Component
|
||||
'database.is_public' => 'nullable|boolean',
|
||||
'database.public_port' => 'nullable|integer',
|
||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||
'database.custom_docker_run_options' => 'nullable',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@ -42,6 +43,7 @@ class General extends Component
|
||||
'database.ports_mappings' => 'Port Mapping',
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@ -54,7 +56,7 @@ public function mount()
|
||||
public function instantSaveAdvanced()
|
||||
{
|
||||
try {
|
||||
if (! $this->server->isLogDrainEnabled()) {
|
||||
if (!$this->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
|
||||
@ -71,14 +73,14 @@ public function instantSaveAdvanced()
|
||||
public function instantSave()
|
||||
{
|
||||
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->database->is_public = false;
|
||||
|
||||
return;
|
||||
}
|
||||
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->database->is_public = false;
|
||||
|
||||
@ -93,7 +95,7 @@ public function instantSave()
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = ! $this->database->is_public;
|
||||
$this->database->is_public = !$this->database->is_public;
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ class General extends Component
|
||||
'database.is_public' => 'nullable|boolean',
|
||||
'database.public_port' => 'nullable|integer',
|
||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||
'database.custom_docker_run_options' => 'nullable',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@ -40,6 +41,7 @@ class General extends Component
|
||||
'database.ports_mappings' => 'Port Mapping',
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@ -52,7 +54,7 @@ public function mount()
|
||||
public function instantSaveAdvanced()
|
||||
{
|
||||
try {
|
||||
if (! $this->server->isLogDrainEnabled()) {
|
||||
if (!$this->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
|
||||
@ -86,14 +88,14 @@ public function submit()
|
||||
public function instantSave()
|
||||
{
|
||||
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->database->is_public = false;
|
||||
|
||||
return;
|
||||
}
|
||||
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->database->is_public = false;
|
||||
|
||||
@ -108,7 +110,7 @@ public function instantSave()
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = ! $this->database->is_public;
|
||||
$this->database->is_public = !$this->database->is_public;
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
@ -31,6 +31,7 @@ class General extends Component
|
||||
'database.is_public' => 'nullable|boolean',
|
||||
'database.public_port' => 'nullable|integer',
|
||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||
'database.custom_docker_run_options' => 'nullable',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@ -42,6 +43,7 @@ class General extends Component
|
||||
'database.ports_mappings' => 'Port Mapping',
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@ -55,7 +57,7 @@ public function mount()
|
||||
public function instantSaveAdvanced()
|
||||
{
|
||||
try {
|
||||
if (! $this->server->isLogDrainEnabled()) {
|
||||
if (!$this->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
|
||||
@ -92,14 +94,14 @@ public function submit()
|
||||
public function instantSave()
|
||||
{
|
||||
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->database->is_public = false;
|
||||
|
||||
return;
|
||||
}
|
||||
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->database->is_public = false;
|
||||
|
||||
@ -114,7 +116,7 @@ public function instantSave()
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = ! $this->database->is_public;
|
||||
$this->database->is_public = !$this->database->is_public;
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ class General extends Component
|
||||
'database.is_public' => 'nullable|boolean',
|
||||
'database.public_port' => 'nullable|integer',
|
||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||
'database.custom_docker_run_options' => 'nullable',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@ -48,6 +49,7 @@ class General extends Component
|
||||
'database.ports_mappings' => 'Port Mapping',
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
'database.custom_docker_run_options' => 'Custom Docker Options',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@ -61,7 +63,7 @@ public function mount()
|
||||
public function instantSaveAdvanced()
|
||||
{
|
||||
try {
|
||||
if (! $this->server->isLogDrainEnabled()) {
|
||||
if (!$this->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
|
||||
@ -98,14 +100,14 @@ public function submit()
|
||||
public function instantSave()
|
||||
{
|
||||
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->database->is_public = false;
|
||||
|
||||
return;
|
||||
}
|
||||
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->database->is_public = false;
|
||||
|
||||
@ -120,7 +122,7 @@ public function instantSave()
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = ! $this->database->is_public;
|
||||
$this->database->is_public = !$this->database->is_public;
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
@ -33,6 +33,7 @@ class General extends Component
|
||||
'database.is_public' => 'nullable|boolean',
|
||||
'database.public_port' => 'nullable|integer',
|
||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||
'database.custom_docker_run_options' => 'nullable',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@ -46,6 +47,7 @@ class General extends Component
|
||||
'database.ports_mappings' => 'Port Mapping',
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@ -59,7 +61,7 @@ public function mount()
|
||||
public function instantSaveAdvanced()
|
||||
{
|
||||
try {
|
||||
if (! $this->server->isLogDrainEnabled()) {
|
||||
if (!$this->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
|
||||
@ -99,14 +101,14 @@ public function submit()
|
||||
public function instantSave()
|
||||
{
|
||||
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->database->is_public = false;
|
||||
|
||||
return;
|
||||
}
|
||||
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->database->is_public = false;
|
||||
|
||||
@ -121,7 +123,7 @@ public function instantSave()
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = ! $this->database->is_public;
|
||||
$this->database->is_public = !$this->database->is_public;
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ class General extends Component
|
||||
'database.is_public' => 'nullable|boolean',
|
||||
'database.public_port' => 'nullable|integer',
|
||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||
'database.custom_docker_run_options' => 'nullable',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@ -48,6 +49,7 @@ class General extends Component
|
||||
'database.ports_mappings' => 'Port Mapping',
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@ -60,7 +62,7 @@ public function mount()
|
||||
public function instantSaveAdvanced()
|
||||
{
|
||||
try {
|
||||
if (! $this->server->isLogDrainEnabled()) {
|
||||
if (!$this->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
|
||||
@ -97,14 +99,14 @@ public function submit()
|
||||
public function instantSave()
|
||||
{
|
||||
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->database->is_public = false;
|
||||
|
||||
return;
|
||||
}
|
||||
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->database->is_public = false;
|
||||
|
||||
@ -119,7 +121,7 @@ public function instantSave()
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = ! $this->database->is_public;
|
||||
$this->database->is_public = !$this->database->is_public;
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
@ -49,6 +49,7 @@ public function getListeners()
|
||||
'database.is_public' => 'nullable|boolean',
|
||||
'database.public_port' => 'nullable|integer',
|
||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||
'database.custom_docker_run_options' => 'nullable',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@ -65,6 +66,7 @@ public function getListeners()
|
||||
'database.ports_mappings' => 'Port Mapping',
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
|
@ -31,6 +31,7 @@ class General extends Component
|
||||
'database.is_public' => 'nullable|boolean',
|
||||
'database.public_port' => 'nullable|integer',
|
||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||
'database.custom_docker_run_options' => 'nullable',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@ -42,6 +43,7 @@ class General extends Component
|
||||
'database.ports_mappings' => 'Port Mapping',
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
'database.custom_docker_run_options' => 'Custom Docker Options',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@ -55,7 +57,7 @@ public function mount()
|
||||
public function instantSaveAdvanced()
|
||||
{
|
||||
try {
|
||||
if (! $this->server->isLogDrainEnabled()) {
|
||||
if (!$this->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
|
||||
@ -86,14 +88,14 @@ public function submit()
|
||||
public function instantSave()
|
||||
{
|
||||
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->database->is_public = false;
|
||||
|
||||
return;
|
||||
}
|
||||
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->database->is_public = false;
|
||||
|
||||
@ -108,7 +110,7 @@ public function instantSave()
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = ! $this->database->is_public;
|
||||
$this->database->is_public = !$this->database->is_public;
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
@ -5,6 +5,8 @@
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Models\Project;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
@ -58,12 +60,26 @@ public function submit()
|
||||
|
||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->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([
|
||||
'name' => 'service'.Str::random(10),
|
||||
'docker_compose_raw' => $this->dockerComposeRaw,
|
||||
'environment_id' => $environment->id,
|
||||
'server_id' => (int) $server_id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination_class,
|
||||
]);
|
||||
|
||||
$variables = parseEnvFormatToArray($this->envFile);
|
||||
foreach ($variables as $key => $variable) {
|
||||
EnvironmentVariable::create([
|
||||
|
@ -45,6 +45,8 @@ class Select extends Component
|
||||
|
||||
public ?string $selectedEnvironment = null;
|
||||
|
||||
public string $postgresql_type = 'postgres:16-alpine';
|
||||
|
||||
public ?string $existingPostgresqlUrl = null;
|
||||
|
||||
public ?string $search = null;
|
||||
@ -202,6 +204,8 @@ public function setServer(Server $server)
|
||||
$docker = $this->standaloneDockers->first() ?? $this->swarmDockers->first();
|
||||
if ($docker) {
|
||||
$this->setDestination($docker->uuid);
|
||||
|
||||
return $this->whatToDoNext();
|
||||
}
|
||||
}
|
||||
$this->current_step = 'destinations';
|
||||
@ -211,15 +215,38 @@ public function setDestination(string $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', [
|
||||
'project_uuid' => $this->parameters['project_uuid'],
|
||||
'environment_name' => $this->parameters['environment_name'],
|
||||
'type' => $this->type,
|
||||
'destination' => $this->destination_uuid,
|
||||
'server_id' => $this->server_id,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function loadServers()
|
||||
{
|
||||
$this->servers = Server::isUsable()->get();
|
||||
|
@ -18,6 +18,7 @@ public function mount()
|
||||
$type = str(request()->query('type'));
|
||||
$destination_uuid = request()->query('destination');
|
||||
$server_id = request()->query('server_id');
|
||||
$database_image = request()->query('database_image');
|
||||
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (! $project) {
|
||||
@ -33,7 +34,11 @@ public function mount()
|
||||
|
||||
if (in_array($type, DATABASE_TYPES)) {
|
||||
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') {
|
||||
$database = create_standalone_redis($environment->id, $destination_uuid);
|
||||
} elseif ($type->value() === 'mongodb') {
|
||||
|
@ -25,6 +25,7 @@ public function getListeners()
|
||||
return [
|
||||
"echo-private:user.{$userId},ServiceStatusChanged" => 'check_status',
|
||||
'check_status',
|
||||
'refresh' => '$refresh',
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,11 @@ class EditCompose extends Component
|
||||
|
||||
public $serviceId;
|
||||
|
||||
protected $listeners = ['refreshEnvs', 'envsUpdated'];
|
||||
protected $listeners = [
|
||||
'refreshEnvs',
|
||||
'envsUpdated',
|
||||
'refresh' => 'envsUpdated',
|
||||
];
|
||||
|
||||
protected $rules = [
|
||||
'service.docker_compose_raw' => 'required',
|
||||
|
@ -33,6 +33,7 @@ class FileStorage extends Component
|
||||
'fileStorage.fs_path' => 'required',
|
||||
'fileStorage.mount_path' => 'required',
|
||||
'fileStorage.content' => 'nullable',
|
||||
'fileStorage.is_based_on_git' => 'required|boolean',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@ -45,6 +46,7 @@ public function mount()
|
||||
$this->workdir = null;
|
||||
$this->fs_path = $this->fileStorage->fs_path;
|
||||
}
|
||||
$this->fileStorage->loadStorageOnServer();
|
||||
}
|
||||
|
||||
public function convertToDirectory()
|
||||
@ -53,6 +55,7 @@ public function convertToDirectory()
|
||||
$this->fileStorage->deleteStorageOnServer();
|
||||
$this->fileStorage->is_directory = true;
|
||||
$this->fileStorage->content = null;
|
||||
$this->fileStorage->is_based_on_git = false;
|
||||
$this->fileStorage->save();
|
||||
$this->fileStorage->saveStorageOnServer();
|
||||
} catch (\Throwable $e) {
|
||||
@ -68,6 +71,9 @@ public function convertToFile()
|
||||
$this->fileStorage->deleteStorageOnServer();
|
||||
$this->fileStorage->is_directory = false;
|
||||
$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->saveStorageOnServer();
|
||||
} catch (\Throwable $e) {
|
||||
|
@ -53,7 +53,7 @@ public function mount()
|
||||
public function saveCompose($raw)
|
||||
{
|
||||
$this->service->docker_compose_raw = $raw;
|
||||
$this->submit();
|
||||
$this->submit(notify: false);
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
@ -62,7 +62,7 @@ public function instantSave()
|
||||
$this->dispatch('success', 'Service settings saved.');
|
||||
}
|
||||
|
||||
public function submit()
|
||||
public function submit($notify = true)
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
@ -76,7 +76,7 @@ public function submit()
|
||||
$this->service->refresh();
|
||||
$this->service->saveComposeConfigs();
|
||||
$this->dispatch('refreshEnvs');
|
||||
$this->dispatch('success', 'Service saved.');
|
||||
$notify && $this->dispatch('success', 'Service saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
|
@ -23,8 +23,8 @@ class All extends Component
|
||||
public string $view = 'normal';
|
||||
|
||||
protected $listeners = [
|
||||
'refreshEnvs',
|
||||
'saveKey' => 'submit',
|
||||
'environmentVariableDeleted' => 'refreshEnvs',
|
||||
];
|
||||
|
||||
protected $rules = [
|
||||
@ -40,220 +40,240 @@ public function mount()
|
||||
$this->showPreview = true;
|
||||
}
|
||||
$this->modalId = new Cuid2;
|
||||
$this->sortMe();
|
||||
$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();
|
||||
$this->sortEnvironmentVariables();
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose') {
|
||||
$this->resource->settings->save();
|
||||
$this->dispatch('success', 'Environment variable settings updated.');
|
||||
$this->sortMe();
|
||||
$this->resource->settings->save();
|
||||
$this->sortEnvironmentVariables();
|
||||
$this->dispatch('success', 'Environment variable settings updated.');
|
||||
}
|
||||
|
||||
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()
|
||||
{
|
||||
$this->variables = $this->resource->environment_variables->map(function ($item) {
|
||||
$this->variables = $this->formatEnvironmentVariables($this->resource->environment_variables);
|
||||
if ($this->showPreview) {
|
||||
$this->variablesPreview = $this->formatEnvironmentVariables($this->resource->environment_variables_preview);
|
||||
}
|
||||
}
|
||||
|
||||
private function formatEnvironmentVariables($variables)
|
||||
{
|
||||
return $variables->map(function ($item) {
|
||||
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) {
|
||||
return "$item->key=(multiline, edit in normal view)";
|
||||
return "$item->key=(Multiline environment variable, edit in normal view)";
|
||||
}
|
||||
|
||||
return "$item->key=$item->value";
|
||||
})->join('
|
||||
');
|
||||
if ($this->showPreview) {
|
||||
$this->variablesPreview = $this->resource->environment_variables_preview->map(function ($item) {
|
||||
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('
|
||||
');
|
||||
}
|
||||
})->join("\n");
|
||||
}
|
||||
|
||||
public function switch()
|
||||
{
|
||||
if ($this->view === 'normal') {
|
||||
$this->view = 'dev';
|
||||
} else {
|
||||
$this->view = 'normal';
|
||||
}
|
||||
$this->sortMe();
|
||||
$this->view = $this->view === 'normal' ? 'dev' : 'normal';
|
||||
$this->sortEnvironmentVariables();
|
||||
}
|
||||
|
||||
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();
|
||||
try {
|
||||
if ($data === null) {
|
||||
$this->handleBulkSubmit();
|
||||
} else {
|
||||
$found = $this->resource->environment_variables()->where('key', $key)->first();
|
||||
$this->handleSingleSubmit($data);
|
||||
}
|
||||
if ($found) {
|
||||
if ($found->is_shown_once || $found->is_multiline) {
|
||||
continue;
|
||||
|
||||
$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();
|
||||
}
|
||||
$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.');
|
||||
$order++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
$found->save();
|
||||
private function handleBulkSubmit()
|
||||
{
|
||||
$variables = parseEnvFormatToArray($this->variables);
|
||||
$this->deleteRemovedVariables(false, $variables);
|
||||
$this->updateOrCreateVariables(false, $variables);
|
||||
|
||||
continue;
|
||||
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();
|
||||
if ($found) {
|
||||
$this->dispatch('error', 'Environment variable already exists.');
|
||||
|
||||
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->key = $data['key'];
|
||||
$environment->value = $data['value'];
|
||||
$environment->is_build_time = $data['is_build_time'] ?? false;
|
||||
$environment->is_multiline = $data['is_multiline'] ?? false;
|
||||
$environment->is_literal = $data['is_literal'] ?? false;
|
||||
$environment->is_preview = $data['is_preview'] ?? false;
|
||||
|
||||
$resourceType = $this->resource->type();
|
||||
$resourceIdField = $this->getResourceIdField($resourceType);
|
||||
|
||||
if ($resourceIdField) {
|
||||
$environment->$resourceIdField = $this->resource->id;
|
||||
}
|
||||
|
||||
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 = $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->value = $value;
|
||||
$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->is_preview = $isPreview;
|
||||
|
||||
$this->setEnvironmentResourceId($environment);
|
||||
$environment->save();
|
||||
}
|
||||
}
|
||||
if ($isPreview) {
|
||||
$this->dispatch('success', 'Preview environment variables updated.');
|
||||
} else {
|
||||
$this->dispatch('success', 'Environment variables updated.');
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
$this->refreshEnvs();
|
||||
}
|
||||
|
||||
public function refreshEnvs()
|
||||
{
|
||||
$this->resource->refresh();
|
||||
$this->sortEnvironmentVariables();
|
||||
$this->getDevView();
|
||||
}
|
||||
|
||||
public function submit($data)
|
||||
{
|
||||
try {
|
||||
$found = $this->resource->environment_variables()->where('key', $data['key'])->first();
|
||||
if ($found) {
|
||||
$this->dispatch('error', 'Environment variable already exists.');
|
||||
|
||||
return;
|
||||
}
|
||||
$environment = new EnvironmentVariable;
|
||||
$environment->key = $data['key'];
|
||||
$environment->value = $data['value'];
|
||||
$environment->is_build_time = $data['is_build_time'];
|
||||
$environment->is_multiline = $data['is_multiline'];
|
||||
$environment->is_literal = $data['is_literal'];
|
||||
$environment->is_preview = $data['is_preview'];
|
||||
|
||||
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();
|
||||
$this->refreshEnvs();
|
||||
$this->dispatch('success', 'Environment variable added.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,7 +24,8 @@ class Show extends Component
|
||||
public string $type;
|
||||
|
||||
protected $listeners = [
|
||||
'refresh' => 'refresh',
|
||||
'refreshEnvs' => 'refresh',
|
||||
'refresh',
|
||||
'compose_loaded' => '$refresh',
|
||||
];
|
||||
|
||||
@ -129,9 +130,10 @@ public function delete()
|
||||
{
|
||||
try {
|
||||
$this->env->delete();
|
||||
$this->dispatch('refreshEnvs');
|
||||
$this->dispatch('environmentVariableDeleted');
|
||||
$this->dispatch('success', 'Environment variable deleted successfully.');
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -97,7 +97,7 @@ public function getLogs($refresh = false)
|
||||
if (! $refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) {
|
||||
return;
|
||||
}
|
||||
if (! $this->numberOfLines) {
|
||||
if ($this->numberOfLines <= 0) {
|
||||
$this->numberOfLines = 1000;
|
||||
}
|
||||
if ($this->container) {
|
||||
|
@ -26,7 +26,7 @@ public function mount()
|
||||
$this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name'));
|
||||
} elseif ($this->resource->type() == 'application') {
|
||||
if ($this->resource->build_pack === 'dockercompose') {
|
||||
$parsed = $this->resource->parseCompose();
|
||||
$parsed = $this->resource->oldParser();
|
||||
$containers = collect(data_get($parsed, 'services'))->keys();
|
||||
$this->containerNames = $containers;
|
||||
} else {
|
||||
|
@ -19,8 +19,6 @@ class Form extends Component
|
||||
|
||||
public ?string $wildcard_domain = null;
|
||||
|
||||
public int $cleanup_after_percentage;
|
||||
|
||||
public bool $dockerInstallationStarted = false;
|
||||
|
||||
public bool $revalidate = false;
|
||||
@ -38,7 +36,6 @@ class Form extends Component
|
||||
'server.settings.is_swarm_manager' => 'required|boolean',
|
||||
'server.settings.is_swarm_worker' => 'required|boolean',
|
||||
'server.settings.is_build_server' => 'required|boolean',
|
||||
'server.settings.is_force_cleanup_enabled' => 'required|boolean',
|
||||
'server.settings.concurrent_builds' => 'required|integer|min:1',
|
||||
'server.settings.dynamic_timeout' => 'required|integer|min:1',
|
||||
'server.settings.is_metrics_enabled' => 'required|boolean',
|
||||
@ -48,6 +45,9 @@ class Form extends Component
|
||||
'wildcard_domain' => 'nullable|url',
|
||||
'server.settings.is_server_api_enabled' => 'required|boolean',
|
||||
'server.settings.server_timezone' => 'required|string|timezone',
|
||||
'server.settings.force_docker_cleanup' => 'required|boolean',
|
||||
'server.settings.docker_cleanup_frequency' => 'required_if:server.settings.force_docker_cleanup,true|string',
|
||||
'server.settings.docker_cleanup_threshold' => 'required_if:server.settings.force_docker_cleanup,false|integer|min:1|max:100',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@ -78,7 +78,19 @@ public function mount(Server $server)
|
||||
$this->server = $server;
|
||||
$this->timezones = collect(timezone_identifiers_list())->sort()->values()->toArray();
|
||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
||||
$this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
|
||||
$this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
|
||||
}
|
||||
|
||||
public function updated($field)
|
||||
{
|
||||
if ($field === 'server.settings.docker_cleanup_frequency') {
|
||||
$frequency = $this->server->settings->docker_cleanup_frequency;
|
||||
if (empty($frequency) || ! validate_cron_expression($frequency)) {
|
||||
$this->dispatch('error', 'Invalid Cron / Human expression for Docker Cleanup Frequency. Resetting to default 10 minutes.');
|
||||
$this->server->settings->docker_cleanup_frequency = '*/10 * * * *';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function serverInstalled()
|
||||
@ -178,29 +190,33 @@ public function validateServer($install = true)
|
||||
|
||||
public function submit()
|
||||
{
|
||||
if (isCloud() && !isDev()) {
|
||||
$this->validate();
|
||||
$this->validate([
|
||||
'server.ip' => 'required',
|
||||
]);
|
||||
} else {
|
||||
$this->validate();
|
||||
}
|
||||
$uniqueIPs = Server::all()->reject(function (Server $server) {
|
||||
return $server->id === $this->server->id;
|
||||
})->pluck('ip')->toArray();
|
||||
if (in_array($this->server->ip, $uniqueIPs)) {
|
||||
$this->dispatch('error', 'IP address is already in use by another team.');
|
||||
return;
|
||||
}
|
||||
refresh_server_connection($this->server->privateKey);
|
||||
$this->server->settings->wildcard_domain = $this->wildcard_domain;
|
||||
$this->server->settings->cleanup_after_percentage = $this->cleanup_after_percentage;
|
||||
try {
|
||||
if (isCloud() && ! isDev()) {
|
||||
$this->validate();
|
||||
$this->validate([
|
||||
'server.ip' => 'required',
|
||||
]);
|
||||
} else {
|
||||
$this->validate();
|
||||
}
|
||||
$uniqueIPs = Server::all()->reject(function (Server $server) {
|
||||
return $server->id === $this->server->id;
|
||||
})->pluck('ip')->toArray();
|
||||
if (in_array($this->server->ip, $uniqueIPs)) {
|
||||
$this->dispatch('error', 'IP address is already in use by another team.');
|
||||
|
||||
$currentTimezone = $this->server->settings->getOriginal('server_timezone');
|
||||
return;
|
||||
}
|
||||
refresh_server_connection($this->server->privateKey);
|
||||
$this->server->settings->wildcard_domain = $this->wildcard_domain;
|
||||
if ($this->server->settings->force_docker_cleanup) {
|
||||
$this->server->settings->docker_cleanup_frequency = $this->server->settings->docker_cleanup_frequency;
|
||||
} else {
|
||||
$this->server->settings->docker_cleanup_threshold = $this->server->settings->docker_cleanup_threshold;
|
||||
}
|
||||
$currentTimezone = $this->server->settings->getOriginal('server_timezone');
|
||||
$newTimezone = $this->server->settings->server_timezone;
|
||||
|
||||
if ($currentTimezone !== $newTimezone || $currentTimezone === '') {
|
||||
if ($currentTimezone !== $newTimezone || $currentTimezone === '') {
|
||||
try {
|
||||
$timezoneUpdated = $this->updateServerTimezone($newTimezone);
|
||||
if ($timezoneUpdated) {
|
||||
@ -215,9 +231,12 @@ public function submit()
|
||||
}
|
||||
}
|
||||
|
||||
$this->server->settings->save();
|
||||
$this->server->save();
|
||||
$this->dispatch('success', 'Server updated.');
|
||||
$this->server->settings->save();
|
||||
$this->server->save();
|
||||
$this->dispatch('success', 'Server updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedServerTimezone($value)
|
||||
|
@ -16,7 +16,7 @@ class Show extends Component
|
||||
|
||||
public array $parameters;
|
||||
|
||||
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
|
||||
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey'];
|
||||
|
||||
public function saveKey($data)
|
||||
{
|
||||
|
@ -3,6 +3,8 @@
|
||||
namespace App\Models;
|
||||
|
||||
use App\Enums\ApplicationDeploymentStatus;
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Jobs\ServerFilesFromServerJob;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
@ -412,23 +414,6 @@ public function dockerComposeLocation(): Attribute
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return Attribute::make(
|
||||
@ -479,12 +464,12 @@ protected function serverStatus(): Attribute
|
||||
$main_server_status = $this->destination->server->isFunctional();
|
||||
foreach ($additional_servers_status as $status) {
|
||||
$server_status = str($status)->before(':')->value();
|
||||
if ($main_server_status !== $server_status) {
|
||||
if ($server_status !== 'running') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return $main_server_status;
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -1040,7 +1025,7 @@ public function generateGitImportCommands(string $deployment_uuid, int $pull_req
|
||||
}
|
||||
}
|
||||
|
||||
public function parseRawCompose()
|
||||
public function oldRawParser()
|
||||
{
|
||||
try {
|
||||
$yaml = Yaml::parse($this->docker_compose_raw);
|
||||
@ -1100,8 +1085,550 @@ public function parseRawCompose()
|
||||
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) {
|
||||
return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);
|
||||
} else {
|
||||
@ -1154,7 +1681,7 @@ public function loadComposeFile($isInit = false)
|
||||
if ($composeFileContent) {
|
||||
$this->docker_compose_raw = $composeFileContent;
|
||||
$this->save();
|
||||
$parsedServices = $this->parseCompose();
|
||||
$parsedServices = $this->oldParser();
|
||||
if ($this->docker_compose_domains) {
|
||||
$json = collect(json_decode($this->docker_compose_domains));
|
||||
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray();
|
||||
|
@ -14,7 +14,7 @@ protected static function booted()
|
||||
static::deleting(function ($preview) {
|
||||
if ($preview->application->build_pack === 'dockercompose') {
|
||||
$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');
|
||||
$networks = data_get($composeFile, 'networks');
|
||||
$networkKeys = collect($networks)->keys();
|
||||
|
@ -6,7 +6,6 @@
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use OpenApi\Attributes as OA;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
#[OA\Schema(
|
||||
@ -97,8 +96,22 @@ public function resource()
|
||||
$resource = Application::find($this->application_id);
|
||||
} elseif ($this->service_id) {
|
||||
$resource = Service::find($this->service_id);
|
||||
} elseif ($this->database_id) {
|
||||
$resource = getResourceByUuid($this->parameters['database_uuid'], data_get(auth()->user()->currentTeam(), 'id'));
|
||||
} elseif ($this->standalone_postgresql_id) {
|
||||
$resource = StandalonePostgresql::find($this->standalone_postgresql_id);
|
||||
} elseif ($this->standalone_redis_id) {
|
||||
$resource = StandaloneRedis::find($this->standalone_redis_id);
|
||||
} elseif ($this->standalone_mongodb_id) {
|
||||
$resource = StandaloneMongodb::find($this->standalone_mongodb_id);
|
||||
} elseif ($this->standalone_mysql_id) {
|
||||
$resource = StandaloneMysql::find($this->standalone_mysql_id);
|
||||
} elseif ($this->standalone_mariadb_id) {
|
||||
$resource = StandaloneMariadb::find($this->standalone_mariadb_id);
|
||||
} elseif ($this->standalone_keydb_id) {
|
||||
$resource = StandaloneKeydb::find($this->standalone_keydb_id);
|
||||
} elseif ($this->standalone_dragonfly_id) {
|
||||
$resource = StandaloneDragonfly::find($this->standalone_dragonfly_id);
|
||||
} elseif ($this->standalone_clickhouse_id) {
|
||||
$resource = StandaloneClickhouse::find($this->standalone_clickhouse_id);
|
||||
}
|
||||
|
||||
return $resource;
|
||||
@ -122,63 +135,6 @@ public function realValue(): Attribute
|
||||
);
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return Attribute::make(
|
||||
@ -201,8 +157,10 @@ private function get_real_environment_variables(?string $environment_variable =
|
||||
$environment_variable = trim($environment_variable);
|
||||
$sharedEnvsFound = str($environment_variable)->matchAll('/{{(.*?)}}/');
|
||||
if ($sharedEnvsFound->isEmpty()) {
|
||||
|
||||
return $environment_variable;
|
||||
}
|
||||
|
||||
foreach ($sharedEnvsFound as $sharedEnv) {
|
||||
$type = str($sharedEnv)->match('/(.*?)\./');
|
||||
if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||
|
@ -24,8 +24,9 @@ public function service()
|
||||
return $this->morphTo('resource');
|
||||
}
|
||||
|
||||
public function deleteStorageOnServer()
|
||||
public function loadStorageOnServer()
|
||||
{
|
||||
$this->load(['service']);
|
||||
$isService = data_get($this->resource, 'service');
|
||||
if ($isService) {
|
||||
$workdir = $this->resource->service->workdir();
|
||||
@ -35,17 +36,46 @@ public function deleteStorageOnServer()
|
||||
$server = $this->resource->destination->server;
|
||||
}
|
||||
$commands = collect([]);
|
||||
$fs_path = data_get($this, 'fs_path');
|
||||
$isFile = instant_remote_process(["test -f $fs_path && echo OK || echo NOK"], $server);
|
||||
$isDir = instant_remote_process(["test -d $fs_path && echo OK || echo NOK"], $server);
|
||||
if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') {
|
||||
ray($isFile, $isDir);
|
||||
$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);
|
||||
if ($isFile === 'OK') {
|
||||
$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 $fs_path > /dev/null 2>&1 || true");
|
||||
$commands->push("rm -rf $path > /dev/null 2>&1 || true");
|
||||
|
||||
} elseif ($isDir === 'OK') {
|
||||
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true");
|
||||
$commands->push("rmdir $fs_path > /dev/null 2>&1 || true");
|
||||
$commands->push("rm -rf $path > /dev/null 2>&1 || true");
|
||||
$commands->push("rmdir $path > /dev/null 2>&1 || true");
|
||||
}
|
||||
}
|
||||
if ($commands->count() > 0) {
|
||||
@ -55,6 +85,7 @@ public function deleteStorageOnServer()
|
||||
|
||||
public function saveStorageOnServer()
|
||||
{
|
||||
$this->load(['service']);
|
||||
$isService = data_get($this->resource, 'service');
|
||||
if ($isService) {
|
||||
$workdir = $this->resource->service->workdir();
|
||||
@ -74,30 +105,36 @@ public function saveStorageOnServer()
|
||||
$commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true");
|
||||
}
|
||||
}
|
||||
$fileVolume = $this;
|
||||
$path = str(data_get($fileVolume, 'fs_path'));
|
||||
$content = data_get($fileVolume, 'content');
|
||||
$path = data_get_str($this, 'fs_path');
|
||||
$content = data_get($this, 'content');
|
||||
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 ($isFile == 'OK' && $fileVolume->is_directory) {
|
||||
if ($isFile == 'OK' && $this->is_directory) {
|
||||
$content = instant_remote_process(["cat $path"], $server, false);
|
||||
$fileVolume->is_directory = false;
|
||||
$fileVolume->content = $content;
|
||||
$fileVolume->save();
|
||||
$this->is_directory = false;
|
||||
$this->content = $content;
|
||||
$this->save();
|
||||
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.');
|
||||
} elseif ($isDir == 'OK' && ! $fileVolume->is_directory) {
|
||||
$fileVolume->is_directory = true;
|
||||
$fileVolume->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.');
|
||||
} elseif ($isDir == 'OK' && ! $this->is_directory) {
|
||||
if ($path == '/' || $path == '.' || $path == '..' || $path == '' || str($path)->isEmpty() || is_null($path)) {
|
||||
$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.');
|
||||
}
|
||||
instant_remote_process([
|
||||
"rm -fr $path",
|
||||
"touch $path",
|
||||
], $server, false);
|
||||
FileStorageChanged::dispatch(data_get($server, 'team_id'));
|
||||
}
|
||||
if ($isDir == 'NOK' && ! $fileVolume->is_directory) {
|
||||
$chmod = data_get($fileVolume, 'chmod');
|
||||
$chown = data_get($fileVolume, 'chown');
|
||||
if ($isDir == 'NOK' && ! $this->is_directory) {
|
||||
$chmod = data_get($this, 'chmod');
|
||||
$chown = data_get($this, 'chown');
|
||||
if ($content) {
|
||||
$content = base64_encode($content);
|
||||
$commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
|
||||
@ -111,7 +148,7 @@ public function saveStorageOnServer()
|
||||
if ($chmod) {
|
||||
$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");
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ public function latest_log(): HasOne
|
||||
|
||||
public function executions(): HasMany
|
||||
{
|
||||
return $this->hasMany(ScheduledTaskExecution::class);
|
||||
return $this->hasMany(ScheduledTaskExecution::class)->orderBy('created_at', 'desc');
|
||||
}
|
||||
|
||||
public function server()
|
||||
|
@ -649,7 +649,7 @@ public function isServerReady(int $tries = 3)
|
||||
}
|
||||
}
|
||||
|
||||
public function getDiskUsage()
|
||||
public function getDiskUsage(): ?string
|
||||
{
|
||||
return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false);
|
||||
}
|
||||
|
@ -10,10 +10,10 @@
|
||||
type: 'object',
|
||||
properties: [
|
||||
'id' => ['type' => 'integer'],
|
||||
'cleanup_after_percentage' => ['type' => 'integer'],
|
||||
'concurrent_builds' => ['type' => 'integer'],
|
||||
'dynamic_timeout' => ['type' => 'integer'],
|
||||
'force_disabled' => ['type' => 'boolean'],
|
||||
'force_server_cleanup' => ['type' => 'boolean'],
|
||||
'is_build_server' => ['type' => 'boolean'],
|
||||
'is_cloudflare_tunnel' => ['type' => 'boolean'],
|
||||
'is_jump_server' => ['type' => 'boolean'],
|
||||
@ -37,6 +37,8 @@
|
||||
'metrics_history_days' => ['type' => 'integer'],
|
||||
'metrics_refresh_rate_seconds' => ['type' => 'integer'],
|
||||
'metrics_token' => ['type' => 'string'],
|
||||
'docker_cleanup_frequency' => ['type' => 'string'],
|
||||
'docker_cleanup_threshold' => ['type' => 'integer'],
|
||||
'server_id' => ['type' => 'integer'],
|
||||
'wildcard_domain' => ['type' => 'string'],
|
||||
'created_at' => ['type' => 'string'],
|
||||
@ -47,8 +49,13 @@ class ServerSetting extends Model
|
||||
{
|
||||
protected $guarded = [];
|
||||
|
||||
protected $casts = [
|
||||
'force_docker_cleanup' => 'boolean',
|
||||
'docker_cleanup_threshold' => 'integer',
|
||||
];
|
||||
|
||||
public function server()
|
||||
{
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
}
|
||||
}
|
@ -23,6 +23,7 @@
|
||||
'description' => ['type' => 'string', 'description' => 'The description of the service.'],
|
||||
'docker_compose_raw' => ['type' => 'string', 'description' => 'The raw docker-compose.yml file of the service.'],
|
||||
'docker_compose' => ['type' => 'string', 'description' => 'The docker-compose.yml file that is parsed and modified by Coolify.'],
|
||||
'destination_type' => ['type' => 'string', 'description' => 'Destination type.'],
|
||||
'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
|
||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'],
|
||||
@ -205,6 +206,41 @@ public function extraFields()
|
||||
foreach ($applications as $application) {
|
||||
$image = str($application->image)->before(':')->value();
|
||||
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'):
|
||||
$data = collect([]);
|
||||
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first();
|
||||
@ -504,6 +540,9 @@ public function extraFields()
|
||||
default:
|
||||
$data = collect([]);
|
||||
$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();
|
||||
if ($admin_user) {
|
||||
$data = $data->merge([
|
||||
@ -525,6 +564,15 @@ public function extraFields()
|
||||
],
|
||||
]);
|
||||
}
|
||||
if ($admin_email) {
|
||||
$data = $data->merge([
|
||||
'Email' => [
|
||||
'key' => 'ADMIN_EMAIL',
|
||||
'value' => data_get($admin_email, 'value'),
|
||||
'rules' => 'required|email',
|
||||
],
|
||||
]);
|
||||
}
|
||||
$fields->put('Admin', $data->toArray());
|
||||
break;
|
||||
case str($image)?->contains('vaultwarden'):
|
||||
|
@ -44,7 +44,7 @@ public function via(object $notifiable): array
|
||||
// $mail->view('emails.high-disk-usage', [
|
||||
// 'name' => $this->server->name,
|
||||
// 'disk_usage' => $this->disk_usage,
|
||||
// 'threshold' => $this->cleanup_after_percentage,
|
||||
// 'threshold' => $this->docker_cleanup_threshold,
|
||||
// ]);
|
||||
// return $mail;
|
||||
// }
|
||||
|
@ -17,7 +17,7 @@ class HighDiskUsage extends Notification implements ShouldQueue
|
||||
|
||||
public $tries = 1;
|
||||
|
||||
public function __construct(public Server $server, public int $disk_usage, public int $cleanup_after_percentage) {}
|
||||
public function __construct(public Server $server, public int $disk_usage, public int $docker_cleanup_threshold) {}
|
||||
|
||||
public function via(object $notifiable): array
|
||||
{
|
||||
@ -46,7 +46,7 @@ public function toMail(): MailMessage
|
||||
$mail->view('emails.high-disk-usage', [
|
||||
'name' => $this->server->name,
|
||||
'disk_usage' => $this->disk_usage,
|
||||
'threshold' => $this->cleanup_after_percentage,
|
||||
'threshold' => $this->docker_cleanup_threshold,
|
||||
]);
|
||||
|
||||
return $mail;
|
||||
@ -54,7 +54,7 @@ public function toMail(): MailMessage
|
||||
|
||||
public function toDiscord(): string
|
||||
{
|
||||
$message = "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.";
|
||||
$message = "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->docker_cleanup_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.";
|
||||
|
||||
return $message;
|
||||
}
|
||||
@ -62,7 +62,7 @@ public function toDiscord(): string
|
||||
public function toTelegram(): array
|
||||
{
|
||||
return [
|
||||
'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->cleanup_after_percentage}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.",
|
||||
'message' => "Coolify: Server '{$this->server->name}' high disk usage detected!\nDisk usage: {$this->disk_usage}%. Threshold: {$this->docker_cleanup_threshold}%.\nPlease cleanup your disk to prevent data-loss.\nHere are some tips: https://coolify.io/docs/knowledge-base/server/automated-cleanup.",
|
||||
];
|
||||
}
|
||||
}
|
||||
|
67
app/Providers/TelescopeServiceProvider.php
Normal 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,
|
||||
]);
|
||||
});
|
||||
}
|
||||
}
|
@ -91,7 +91,7 @@ function next_queuable(string $server_id, string $application_id): bool
|
||||
$server = Server::find($server_id);
|
||||
$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) {
|
||||
return false;
|
||||
|
@ -19,7 +19,7 @@ function generate_database_name(string $type): string
|
||||
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();
|
||||
if (! $destination) {
|
||||
@ -27,6 +27,7 @@ function create_standalone_postgresql($environmentId, $destinationUuid, ?array $
|
||||
}
|
||||
$database = new StandalonePostgresql;
|
||||
$database->name = generate_database_name('postgresql');
|
||||
$database->image = $databaseImage;
|
||||
$database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||
$database->environment_id = $environmentId;
|
||||
$database->destination_id = $destination->id;
|
||||
|
@ -140,6 +140,8 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
|
||||
|
||||
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;
|
||||
$now = now()->format('Hisu');
|
||||
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',
|
||||
'--ulimit',
|
||||
'--device',
|
||||
'--shm-size',
|
||||
]);
|
||||
$mapping = collect([
|
||||
'--cap-add' => 'cap_add',
|
||||
'--cap-drop' => 'cap_drop',
|
||||
'--security-opt' => 'security_opt',
|
||||
'--sysctl' => 'sysctls',
|
||||
'--ulimit' => 'ulimits',
|
||||
'--device' => 'devices',
|
||||
'--init' => 'init',
|
||||
'--ulimit' => 'ulimits',
|
||||
'--privileged' => 'privileged',
|
||||
'--ip' => 'ip',
|
||||
'--shm-size' => 'shm_size',
|
||||
]);
|
||||
foreach ($matches as $match) {
|
||||
$option = $match[1];
|
||||
@ -704,6 +707,7 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
||||
$options = collect($options);
|
||||
// Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js
|
||||
foreach ($options as $option => $value) {
|
||||
// ray($option,$value);
|
||||
if (! data_get($mapping, $option)) {
|
||||
continue;
|
||||
}
|
||||
@ -728,6 +732,10 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
||||
}
|
||||
});
|
||||
$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 {
|
||||
if ($list_options->contains($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();
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
return 'OK';
|
||||
|
@ -151,7 +151,7 @@ function generate_default_proxy_configuration(Server $server)
|
||||
'services' => [
|
||||
'traefik' => [
|
||||
'container_name' => 'coolify-proxy',
|
||||
'image' => 'traefik:v2.11',
|
||||
'image' => 'traefik:v3.1',
|
||||
'restart' => RESTART_MODE,
|
||||
'extra_hosts' => [
|
||||
'host.docker.internal:host-gateway',
|
||||
|
@ -170,7 +170,7 @@ function generateSshCommand(Server $server, string $command)
|
||||
// ray($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');
|
||||
if ($command instanceof Collection) {
|
||||
|
@ -53,7 +53,9 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli
|
||||
if ($isFile == 'OK') {
|
||||
// If its a file & exists
|
||||
$filesystemContent = instant_remote_process(["cat $fileLocation"], $server);
|
||||
$fileVolume->content = $filesystemContent;
|
||||
if ($fileVolume->is_based_on_git) {
|
||||
$fileVolume->content = $filesystemContent;
|
||||
}
|
||||
$fileVolume->is_directory = false;
|
||||
$fileVolume->save();
|
||||
} elseif ($isDir == 'OK') {
|
||||
|
@ -18,6 +18,7 @@
|
||||
"laravel/prompts": "^0.1.6",
|
||||
"laravel/sanctum": "^v4.0",
|
||||
"laravel/socialite": "^v5.14.0",
|
||||
"laravel/telescope": "^5.2",
|
||||
"laravel/tinker": "^v2.8.1",
|
||||
"laravel/ui": "^4.2",
|
||||
"lcobucci/jwt": "^5.0.0",
|
||||
@ -93,7 +94,9 @@
|
||||
},
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"dont-discover": []
|
||||
"dont-discover": [
|
||||
"laravel/telescope"
|
||||
]
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
|
71
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "cb17445966de6094aef5a92ee59d1d77",
|
||||
"content-hash": "213fa37e1cc796508e32069c26f3c84a",
|
||||
"packages": [
|
||||
{
|
||||
"name": "amphp/amp",
|
||||
@ -3388,6 +3388,75 @@
|
||||
},
|
||||
"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",
|
||||
"version": "v2.9.0",
|
||||
|
@ -199,6 +199,7 @@
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\HorizonServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\TelescopeServiceProvider::class,
|
||||
|
||||
],
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
|
||||
// The release version of your application
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||
'release' => '4.0.0-beta.323',
|
||||
'release' => '4.0.0-beta.324',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
206
config/telescope.php
Normal 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),
|
||||
],
|
||||
];
|
@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.323';
|
||||
return '4.0.0-beta.324';
|
||||
|
@ -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');
|
||||
}
|
||||
};
|
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
use App\Models\ServerSetting;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
class AddServerCleanupFieldsToServerSettingsTable extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function up()
|
||||
{
|
||||
$serverSettings = ServerSetting::all();
|
||||
|
||||
Schema::table('server_settings', function (Blueprint $table) {
|
||||
$table->boolean('force_docker_cleanup')->default(false);
|
||||
$table->string('docker_cleanup_frequency')->default('*/10 * * * *');
|
||||
$table->integer('docker_cleanup_threshold')->default(80);
|
||||
|
||||
// Remove old columns
|
||||
$table->dropColumn('cleanup_after_percentage');
|
||||
$table->dropColumn('is_force_cleanup_enabled');
|
||||
});
|
||||
foreach ($serverSettings as $serverSetting) {
|
||||
$serverSetting->force_docker_cleanup = $serverSetting->is_force_cleanup_enabled;
|
||||
$serverSetting->docker_cleanup_threshold = $serverSetting->cleanup_after_percentage;
|
||||
$serverSetting->save();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function down()
|
||||
{
|
||||
$serverSettings = ServerSetting::all();
|
||||
Schema::table('server_settings', function (Blueprint $table) {
|
||||
$table->dropColumn('force_docker_cleanup');
|
||||
$table->dropColumn('docker_cleanup_frequency');
|
||||
$table->dropColumn('docker_cleanup_threshold');
|
||||
|
||||
// Add back old columns
|
||||
$table->integer('cleanup_after_percentage')->default(80);
|
||||
$table->boolean('force_server_cleanup')->default(false);
|
||||
$table->boolean('is_force_cleanup_enabled')->default(false);
|
||||
});
|
||||
foreach ($serverSettings as $serverSetting) {
|
||||
$serverSetting->is_force_cleanup_enabled = $serverSetting->force_docker_cleanup;
|
||||
$serverSetting->cleanup_after_percentage = $serverSetting->docker_cleanup_threshold;
|
||||
$serverSetting->save();
|
||||
}
|
||||
}
|
||||
}
|
@ -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');
|
||||
});
|
||||
}
|
||||
};
|
@ -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');
|
||||
});
|
||||
}
|
||||
};
|
@ -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');
|
||||
});
|
||||
}
|
||||
};
|
@ -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');
|
||||
});
|
||||
}
|
||||
};
|
@ -12,11 +12,13 @@ class StandaloneDockerSeeder extends Seeder
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
StandaloneDocker::create([
|
||||
'id' => 0,
|
||||
'name' => 'Standalone Docker 1',
|
||||
'network' => 'coolify',
|
||||
'server_id' => 0,
|
||||
]);
|
||||
if (StandaloneDocker::find(0) == null) {
|
||||
StandaloneDocker::create([
|
||||
'id' => 0,
|
||||
'name' => 'Standalone Docker 1',
|
||||
'network' => 'coolify',
|
||||
'server_id' => 0,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
15
openapi.yaml
@ -247,13 +247,13 @@ paths:
|
||||
security:
|
||||
-
|
||||
bearerAuth: []
|
||||
/applications/private-gh-app:
|
||||
/applications/private-github-app:
|
||||
post:
|
||||
tags:
|
||||
- Applications
|
||||
summary: 'Create (Private - GH App)'
|
||||
description: 'Create new application based on a private repository through a Github App.'
|
||||
operationId: 4d46c84bda4f1a411f6dda15fce4061f
|
||||
operationId: 8b7af9c9a509385963bf3e72eeeea786
|
||||
requestBody:
|
||||
description: 'Application object that needs to be created.'
|
||||
required: true
|
||||
@ -4523,14 +4523,14 @@ components:
|
||||
properties:
|
||||
id:
|
||||
type: integer
|
||||
cleanup_after_percentage:
|
||||
type: integer
|
||||
concurrent_builds:
|
||||
type: integer
|
||||
dynamic_timeout:
|
||||
type: integer
|
||||
force_disabled:
|
||||
type: boolean
|
||||
force_server_cleanup:
|
||||
type: boolean
|
||||
is_build_server:
|
||||
type: boolean
|
||||
is_cloudflare_tunnel:
|
||||
@ -4577,6 +4577,10 @@ components:
|
||||
type: integer
|
||||
metrics_token:
|
||||
type: string
|
||||
docker_cleanup_frequency:
|
||||
type: string
|
||||
docker_cleanup_threshold:
|
||||
type: integer
|
||||
server_id:
|
||||
type: integer
|
||||
wildcard_domain:
|
||||
@ -4613,6 +4617,9 @@ components:
|
||||
docker_compose:
|
||||
type: string
|
||||
description: 'The docker-compose.yml file that is parsed and modified by Coolify.'
|
||||
destination_type:
|
||||
type: string
|
||||
description: 'Destination type.'
|
||||
destination_id:
|
||||
type: integer
|
||||
description: 'The unique identifier of the destination where the service is running.'
|
||||
|
2
other/logos/hostinger.svg
Normal file
After Width: | Height: | Size: 5.0 KiB |
12
other/logos/jobscollider.svg
Normal file
After Width: | Height: | Size: 12 KiB |
21
public/svgs/budibase.svg
Normal 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
After Width: | Height: | Size: 5.1 KiB |
1
public/svgs/rabbitmq.svg
Normal 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
@ -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
7
public/vendor/telescope/app.css
vendored
Normal file
2
public/vendor/telescope/app.js
vendored
Normal file
BIN
public/vendor/telescope/favicon.ico
vendored
Normal file
After Width: | Height: | Size: 26 KiB |
5
public/vendor/telescope/mix-manifest.json
vendored
Normal 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"
|
||||
}
|
@ -28,16 +28,16 @@
|
||||
<div class="flex flex-col justify-center flex-1">
|
||||
<div class="box-title">{{ $project->name }}</div>
|
||||
<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 class="flex items-center justify-center gap-2 text-xs font-bold">
|
||||
<a class="hover:underline"
|
||||
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
|
||||
<span class="p-2 font-bold">+
|
||||
Add Resource</span>
|
||||
href="{{ route('project.resource.create', ['project_uuid' => $project->uuid, 'environment_name' => data_get($project, 'default_environment()', 'production')]) }}">
|
||||
<span class="p-2 font-bold">+ Add Resource</span>
|
||||
</a>
|
||||
<a class="hover:underline"
|
||||
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
||||
href="{{ route('project.edit', ['project_uuid' => $project->uuid]) }}">
|
||||
Settings
|
||||
</a>
|
||||
</div>
|
||||
@ -159,12 +159,14 @@
|
||||
</div>
|
||||
@endif
|
||||
|
||||
|
||||
<script>
|
||||
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 + '/' + environment;
|
||||
}
|
||||
</script>
|
||||
{{-- <x-forms.button wire:click='getIptables'>Get IPTABLES</x-forms.button> --}}
|
||||
|
@ -254,6 +254,11 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
|
||||
helper="You need to modify the docker compose file." monacoEditorLanguage="yaml"
|
||||
useMonacoEditor />
|
||||
@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"
|
||||
label="Docker Compose Content" helper="You need to modify the docker compose file."
|
||||
monacoEditorLanguage="yaml" useMonacoEditor />
|
||||
|
@ -21,14 +21,18 @@
|
||||
readonly helper="You can only change this in the database." />
|
||||
</div>
|
||||
@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.
|
||||
</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="Password" id="database.clickhouse_admin_password" type="password" required />
|
||||
</div>
|
||||
@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">
|
||||
<h3 class="py-2">Network</h3>
|
||||
<div class="flex items-end gap-2">
|
||||
|
@ -11,6 +11,10 @@
|
||||
<x-forms.input label="Description" id="database.description" />
|
||||
<x-forms.input label="Image" id="database.image" required />
|
||||
</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">
|
||||
<h3 class="py-2">Network</h3>
|
||||
<div class="flex items-end gap-2">
|
||||
|
@ -12,6 +12,10 @@
|
||||
<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>" />
|
||||
</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">
|
||||
<h3 class="py-2">Network</h3>
|
||||
<div class="flex items-end gap-2">
|
||||
|
@ -16,30 +16,40 @@
|
||||
automations (like backups) won't work.
|
||||
</div>
|
||||
@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
|
||||
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
|
||||
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
|
||||
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"
|
||||
placeholder="If empty, it will be the same as Username." readonly
|
||||
helper="You can only change this in the database." />
|
||||
</div>
|
||||
@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"
|
||||
helper="You can only change this in the database." />
|
||||
<x-forms.input label="Normal User" id="database.mariadb_user" required
|
||||
helper="You can only change this in the database." />
|
||||
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
|
||||
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"
|
||||
placeholder="If empty, it will be the same as Username."
|
||||
helper="You can only change this in the database." />
|
||||
</div>
|
||||
@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">
|
||||
<h3 class="py-2">Network</h3>
|
||||
<div class="flex items-end gap-2">
|
||||
|
@ -16,7 +16,7 @@
|
||||
automations (like backups) won't work.
|
||||
</div>
|
||||
@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"
|
||||
placeholder="If empty: postgres"
|
||||
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." />
|
||||
</div>
|
||||
@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"
|
||||
placeholder="If empty: postgres" />
|
||||
<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." />
|
||||
</div>
|
||||
@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">
|
||||
<h3 class="py-2">Network</h3>
|
||||
<div class="flex items-end gap-2">
|
||||
|
@ -16,30 +16,40 @@
|
||||
automations (like backups) won't work.
|
||||
</div>
|
||||
@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
|
||||
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
|
||||
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
|
||||
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"
|
||||
placeholder="If empty, it will be the same as Username." readonly
|
||||
helper="You can only change this in the database." />
|
||||
</div>
|
||||
@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"
|
||||
helper="You can only change this in the database." />
|
||||
<x-forms.input label="Normal User" id="database.mysql_user" required
|
||||
helper="You can only change this in the database." />
|
||||
<x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required
|
||||
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"
|
||||
placeholder="If empty, it will be the same as Username."
|
||||
helper="You can only change this in the database." />
|
||||
</div>
|
||||
@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">
|
||||
<h3 class="py-2">Network</h3>
|
||||
<div class="flex items-end gap-2">
|
||||
|
@ -30,7 +30,7 @@
|
||||
automations (like backups) won't work.
|
||||
</div>
|
||||
@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"
|
||||
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
|
||||
@ -40,7 +40,7 @@
|
||||
helper="You can only change this in the database." />
|
||||
</div>
|
||||
@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="Password" id="database.postgres_password" type="password" required />
|
||||
<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"
|
||||
placeholder="If empty, use default. See in docker docs." />
|
||||
</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">
|
||||
<h3 class="py-2">Network</h3>
|
||||
<div class="flex items-end gap-2">
|
||||
|
@ -12,6 +12,10 @@
|
||||
<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>" />
|
||||
</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">
|
||||
<h3 class="py-2">Network</h3>
|
||||
<div class="flex items-end gap-2">
|
||||
|
@ -11,9 +11,8 @@
|
||||
<div class="subtitle">All your projects are here.</div>
|
||||
<div class="grid gap-2 lg:grid-cols-2">
|
||||
@forelse ($projects as $project)
|
||||
<div class="box group" x-data x-on:click="goto('{{ $project->uuid }}')">
|
||||
<div class="flex flex-col justify-center flex-1 mx-6"
|
||||
onclick="gotoProject('{{ $project->uuid }}','{{ $project->default_environment() }}')">
|
||||
<div class="box group" onclick="gotoProject('{{ $project->uuid }}', '{{ $project->default_environment() }}')">
|
||||
<div class="flex flex-col justify-center flex-1 mx-6">
|
||||
<div class="box-title">{{ $project->name }}</div>
|
||||
<div class="box-description ">
|
||||
{{ $project->description }}</div>
|
||||
@ -33,15 +32,12 @@
|
||||
</div>
|
||||
|
||||
<script>
|
||||
function goto(uuid) {
|
||||
function gotoProject(uuid, environment) {
|
||||
if (environment) {
|
||||
window.location.href = '/project/' + uuid + '/' + environment;
|
||||
} else {
|
||||
window.location.href = '/project/' + uuid;
|
||||
}
|
||||
|
||||
function gotoProject(uuid, environment) {
|
||||
if (!environment) {
|
||||
window.location.href = '/project/' + uuid;
|
||||
}
|
||||
window.location.href = '/project/' + uuid + '/' + environment;
|
||||
}
|
||||
</script>
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
|
@ -617,6 +617,85 @@ class="w-[4.5rem] aspect-square h-[4.5rem] p-2 transition-all duration-200 opaci
|
||||
@endif
|
||||
</div>
|
||||
@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')
|
||||
<form wire:submit='addExistingPostgresql' class="flex items-end gap-4">
|
||||
<x-forms.input placeholder="postgres://username:password@database:5432" label="Database URL"
|
||||
|