Merge branch 'next' into auto-cleanup-improvements
@@ -49,6 +49,8 @@ Special thanks to our biggest sponsors!
|
|||||||
<a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a>
|
<a href="https://blacksmith.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/blacksmith.svg" alt="blacksmith logo" width="200"/></a>
|
||||||
<a href="https://latitude.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/latitude.svg" alt="latitude logo" width="200"/></a>
|
<a href="https://latitude.sh/?ref=coolify.io" target="_blank"><img src="./other/logos/latitude.svg" alt="latitude logo" width="200"/></a>
|
||||||
<a href="https://brand.dev/?ref=coolify.io" target="_blank"><img src="./other/logos/branddev.png" alt="branddev logo" width="200"/></a>
|
<a href="https://brand.dev/?ref=coolify.io" target="_blank"><img src="./other/logos/branddev.png" alt="branddev logo" width="200"/></a>
|
||||||
|
<a href="https://jobscollider.com/remote-jobs?ref=coolify.io" target="_blank"><img src="./other/logos/jobscollider.svg" alt="jobscollider logo" width="200"/></a>
|
||||||
|
<a href="https://hostinger.com?ref=coolify.io" target="_blank"><img src="./other/logos/hostinger.svg" alt="hostinger logo" width="200"/></a>
|
||||||
|
|
||||||
## Github Sponsors ($40+)
|
## Github Sponsors ($40+)
|
||||||
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
<a href="https://serpapi.com/?ref=coolify.io"><img width="60px" alt="SerpAPI" src="https://github.com/serpapi.png"/></a>
|
||||||
@@ -71,8 +73,10 @@ Special thanks to our biggest sponsors!
|
|||||||
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
|
<a href="https://github.com/aniftyco"><img src="https://github.com/aniftyco.png" width="60px" alt="NiftyCo" /></a>
|
||||||
<a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a>
|
<a href="https://github.com/iujlaki"><img src="https://github.com/iujlaki.png" width="60px" alt="Imre Ujlaki" /></a>
|
||||||
<a href="https://il.ly"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
|
<a href="https://il.ly"><img src="https://github.com/Illyism.png" width="60px" alt="Ilias Ism" /></a>
|
||||||
|
<a href="https://www.breakcold.com/?utm_source=coolify.io"><img src="https://github.com/breakcold.png" width="60px" alt="Breakcold" /></a>
|
||||||
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
|
<a href="https://github.com/urtho"><img src="https://github.com/urtho.png" width="60px" alt="Paweł Pierścionek" /></a>
|
||||||
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
|
<a href="https://github.com/monocursive"><img src="https://github.com/monocursive.png" width="60px" alt="Michael Mazurczak" /></a>
|
||||||
|
<a href="https://formbricks.com/?utm_source=coolify.io"><img src="https://github.com/formbricks.png" width="60px" alt="Formbricks" /></a>
|
||||||
|
|
||||||
## Organizations
|
## Organizations
|
||||||
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
<a href="https://opencollective.com/coollabsio/organization/0/website"><img src="https://opencollective.com/coollabsio/organization/0/avatar.svg"></a>
|
||||||
|
@@ -21,7 +21,7 @@ class StartClickhouse
|
|||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -75,7 +75,7 @@ class StartClickhouse
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (! is_null($this->database->limits_cpuset)) {
|
if (!is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -102,6 +102,11 @@ class StartClickhouse
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -120,10 +125,10 @@ class StartClickhouse
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,11 +159,11 @@ class StartClickhouse
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('CLICKHOUSE_ADMIN_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("CLICKHOUSE_ADMIN_USER={$this->database->clickhouse_admin_user}");
|
$environment_variables->push("CLICKHOUSE_ADMIN_USER={$this->database->clickhouse_admin_user}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('CLICKHOUSE_ADMIN_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('CLICKHOUSE_ADMIN_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}");
|
$environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,7 +23,7 @@ class StartDragonfly
|
|||||||
$startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}";
|
$startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}";
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -75,7 +75,7 @@ class StartDragonfly
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (! is_null($this->database->limits_cpuset)) {
|
if (!is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -102,6 +102,11 @@ class StartDragonfly
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -120,10 +125,10 @@ class StartDragonfly
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +159,7 @@ class StartDragonfly
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -24,7 +24,7 @@ class StartKeydb
|
|||||||
$startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes";
|
$startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes";
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -74,7 +74,7 @@ class StartKeydb
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (! is_null($this->database->limits_cpuset)) {
|
if (!is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -101,15 +101,19 @@ class StartKeydb
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (! is_null($this->database->keydb_conf) || ! empty($this->database->keydb_conf)) {
|
if (!is_null($this->database->keydb_conf) || !empty($this->database->keydb_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/keydb.conf',
|
'source' => $this->configuration_dir . '/keydb.conf',
|
||||||
'target' => '/etc/keydb/keydb.conf',
|
'target' => '/etc/keydb/keydb.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
$docker_compose['services'][$container_name]['command'] = "keydb-server /etc/keydb/keydb.conf --requirepass {$this->database->keydb_password} --appendonly yes";
|
$docker_compose['services'][$container_name]['command'] = "keydb-server /etc/keydb/keydb.conf --requirepass {$this->database->keydb_password} --appendonly yes";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -128,10 +132,10 @@ class StartKeydb
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +166,7 @@ class StartKeydb
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,7 +21,7 @@ class StartMariadb
|
|||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -69,7 +69,7 @@ class StartMariadb
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (! is_null($this->database->limits_cpuset)) {
|
if (!is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -96,14 +96,19 @@ class StartMariadb
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (! is_null($this->database->mariadb_conf) || ! empty($this->database->mariadb_conf)) {
|
if (!is_null($this->database->mariadb_conf) || !empty($this->database->mariadb_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/custom-config.cnf',
|
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||||
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -122,10 +127,10 @@ class StartMariadb
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,18 +161,18 @@ class StartMariadb
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
|
$environment_variables->push("MARIADB_ROOT_PASSWORD={$this->database->mariadb_root_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
|
$environment_variables->push("MARIADB_DATABASE={$this->database->mariadb_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
|
$environment_variables->push("MARIADB_USER={$this->database->mariadb_user}");
|
||||||
}
|
}
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('MARIADB_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
$environment_variables->push("MARIADB_PASSWORD={$this->database->mariadb_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -23,7 +23,7 @@ class StartMongodb
|
|||||||
$startCommand = 'mongod';
|
$startCommand = 'mongod';
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -77,7 +77,7 @@ class StartMongodb
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (! is_null($this->database->limits_cpuset)) {
|
if (!is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -104,23 +104,27 @@ class StartMongodb
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (! is_null($this->database->mongo_conf) || ! empty($this->database->mongo_conf)) {
|
if (!is_null($this->database->mongo_conf) || !empty($this->database->mongo_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/mongod.conf',
|
'source' => $this->configuration_dir . '/mongod.conf',
|
||||||
'target' => '/etc/mongo/mongod.conf',
|
'target' => '/etc/mongo/mongod.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
$docker_compose['services'][$container_name]['command'] = $startCommand.' --config /etc/mongo/mongod.conf';
|
$docker_compose['services'][$container_name]['command'] = $startCommand . ' --config /etc/mongo/mongod.conf';
|
||||||
}
|
}
|
||||||
$this->add_default_database();
|
$this->add_default_database();
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/docker-entrypoint-initdb.d',
|
'source' => $this->configuration_dir . '/docker-entrypoint-initdb.d',
|
||||||
'target' => '/docker-entrypoint-initdb.d',
|
'target' => '/docker-entrypoint-initdb.d',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -139,10 +143,10 @@ class StartMongodb
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,15 +177,15 @@ class StartMongodb
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_ROOT_USERNAME'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
|
$environment_variables->push("MONGO_INITDB_ROOT_USERNAME={$this->database->mongo_initdb_root_username}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
|
$environment_variables->push("MONGO_INITDB_ROOT_PASSWORD={$this->database->mongo_initdb_root_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('MONGO_INITDB_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
$environment_variables->push("MONGO_INITDB_DATABASE={$this->database->mongo_initdb_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -21,7 +21,7 @@ class StartMysql
|
|||||||
$this->database = $database;
|
$this->database = $database;
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -69,7 +69,7 @@ class StartMysql
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (! is_null($this->database->limits_cpuset)) {
|
if (!is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -96,14 +96,19 @@ class StartMysql
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (! is_null($this->database->mysql_conf) || ! empty($this->database->mysql_conf)) {
|
if (!is_null($this->database->mysql_conf) || !empty($this->database->mysql_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/custom-config.cnf',
|
'source' => $this->configuration_dir . '/custom-config.cnf',
|
||||||
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
'target' => '/etc/mysql/conf.d/custom-config.cnf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -122,10 +127,10 @@ class StartMysql
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -156,18 +161,18 @@ class StartMysql
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_ROOT_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
|
$environment_variables->push("MYSQL_ROOT_PASSWORD={$this->database->mysql_root_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_DATABASE'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
|
$environment_variables->push("MYSQL_DATABASE={$this->database->mysql_database}");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_USER'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
|
$environment_variables->push("MYSQL_USER={$this->database->mysql_user}");
|
||||||
}
|
}
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('MYSQL_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
$environment_variables->push("MYSQL_PASSWORD={$this->database->mysql_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -37,6 +37,7 @@ class StartPostgresql
|
|||||||
$this->generate_init_scripts();
|
$this->generate_init_scripts();
|
||||||
$this->add_custom_conf();
|
$this->add_custom_conf();
|
||||||
|
|
||||||
|
|
||||||
$docker_compose = [
|
$docker_compose = [
|
||||||
'services' => [
|
'services' => [
|
||||||
$container_name => [
|
$container_name => [
|
||||||
@@ -126,6 +127,10 @@ class StartPostgresql
|
|||||||
'config_file=/etc/postgresql/postgresql.conf',
|
'config_file=/etc/postgresql/postgresql.conf',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
|
@@ -24,7 +24,7 @@ class StartRedis
|
|||||||
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
||||||
|
|
||||||
$container_name = $this->database->uuid;
|
$container_name = $this->database->uuid;
|
||||||
$this->configuration_dir = database_configuration_dir().'/'.$container_name;
|
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||||
|
|
||||||
$this->commands = [
|
$this->commands = [
|
||||||
"echo 'Starting {$database->name}.'",
|
"echo 'Starting {$database->name}.'",
|
||||||
@@ -78,7 +78,7 @@ class StartRedis
|
|||||||
],
|
],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
if (! is_null($this->database->limits_cpuset)) {
|
if (!is_null($this->database->limits_cpuset)) {
|
||||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||||
}
|
}
|
||||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||||
@@ -105,15 +105,20 @@ class StartRedis
|
|||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
}
|
}
|
||||||
if (! is_null($this->database->redis_conf) || ! empty($this->database->redis_conf)) {
|
if (!is_null($this->database->redis_conf) || !empty($this->database->redis_conf)) {
|
||||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||||
'type' => 'bind',
|
'type' => 'bind',
|
||||||
'source' => $this->configuration_dir.'/redis.conf',
|
'source' => $this->configuration_dir . '/redis.conf',
|
||||||
'target' => '/usr/local/etc/redis/redis.conf',
|
'target' => '/usr/local/etc/redis/redis.conf',
|
||||||
'read_only' => true,
|
'read_only' => true,
|
||||||
];
|
];
|
||||||
$docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
|
$docker_compose['services'][$container_name]['command'] = "redis-server /usr/local/etc/redis/redis.conf --requirepass {$this->database->redis_password} --appendonly yes";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add custom docker run options
|
||||||
|
$docker_run_options = convert_docker_run_to_compose($this->database->custom_docker_run_options);
|
||||||
|
$docker_compose = generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $this->database->destination->network);
|
||||||
|
|
||||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$docker_compose_base64 = base64_encode($docker_compose);
|
$docker_compose_base64 = base64_encode($docker_compose);
|
||||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d | tee $this->configuration_dir/docker-compose.yml > /dev/null";
|
||||||
@@ -132,10 +137,10 @@ class StartRedis
|
|||||||
$local_persistent_volumes = [];
|
$local_persistent_volumes = [];
|
||||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||||
$local_persistent_volumes[] = $persistentStorage->host_path.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
||||||
} else {
|
} else {
|
||||||
$volume_name = $persistentStorage->name;
|
$volume_name = $persistentStorage->name;
|
||||||
$local_persistent_volumes[] = $volume_name.':'.$persistentStorage->mount_path;
|
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,7 +171,7 @@ class StartRedis
|
|||||||
$environment_variables->push("$env->key=$env->real_value");
|
$environment_variables->push("$env->key=$env->real_value");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($environment_variables->filter(fn ($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
if ($environment_variables->filter(fn($env) => str($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||||
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -9,8 +9,9 @@ class CleanupDocker
|
|||||||
{
|
{
|
||||||
use AsAction;
|
use AsAction;
|
||||||
|
|
||||||
public function handle(Server $server, bool $force = true)
|
public function handle(Server $server)
|
||||||
{
|
{
|
||||||
|
|
||||||
$commands = $this->getCommands($force);
|
$commands = $this->getCommands($force);
|
||||||
|
|
||||||
foreach ($commands as $command) {
|
foreach ($commands as $command) {
|
||||||
@@ -28,7 +29,7 @@ class CleanupDocker
|
|||||||
|
|
||||||
if ($force) {
|
if ($force) {
|
||||||
return array_merge([
|
return array_merge([
|
||||||
'docker container rm $(docker container ls -aq --filter status=exited --filter "label=coolify.managed=true")',
|
'docker container prune -f --filter "label=coolify.managed=true"',
|
||||||
'docker image prune -af',
|
'docker image prune -af',
|
||||||
'docker builder prune -af',
|
'docker builder prune -af',
|
||||||
], $commonCommands);
|
], $commonCommands);
|
||||||
|
@@ -26,7 +26,7 @@ class UpdateCoolify
|
|||||||
if (! $this->server) {
|
if (! $this->server) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
CleanupDocker::dispatch($this->server, false)->onQueue('high');
|
CleanupDocker::dispatch($this->server)->onQueue('high');
|
||||||
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
$response = Http::retry(3, 1000)->get('https://cdn.coollabs.io/coolify/versions.json');
|
||||||
if ($response->successful()) {
|
if ($response->successful()) {
|
||||||
$versions = $response->json();
|
$versions = $response->json();
|
||||||
|
@@ -132,6 +132,9 @@ class Init extends Command
|
|||||||
|
|
||||||
private function cleanup_unused_network_from_coolify_proxy()
|
private function cleanup_unused_network_from_coolify_proxy()
|
||||||
{
|
{
|
||||||
|
if (isCloud()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
foreach ($this->servers as $server) {
|
foreach ($this->servers as $server) {
|
||||||
if (! $server->isFunctional()) {
|
if (! $server->isFunctional()) {
|
||||||
continue;
|
continue;
|
||||||
|
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,6 +30,7 @@ class Kernel extends ConsoleKernel
|
|||||||
$this->all_servers = Server::all();
|
$this->all_servers = Server::all();
|
||||||
$settings = InstanceSettings::get();
|
$settings = InstanceSettings::get();
|
||||||
|
|
||||||
|
$schedule->command('telescope:prune')->daily();
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
// Instance Jobs
|
// Instance Jobs
|
||||||
$schedule->command('horizon:snapshot')->everyMinute();
|
$schedule->command('horizon:snapshot')->everyMinute();
|
||||||
|
@@ -201,7 +201,7 @@ class ApplicationsController extends Controller
|
|||||||
#[OA\Post(
|
#[OA\Post(
|
||||||
summary: 'Create (Private - GH App)',
|
summary: 'Create (Private - GH App)',
|
||||||
description: 'Create new application based on a private repository through a Github App.',
|
description: 'Create new application based on a private repository through a Github App.',
|
||||||
path: '/applications/private-gh-app',
|
path: '/applications/private-github-app',
|
||||||
security: [
|
security: [
|
||||||
['bearerAuth' => []],
|
['bearerAuth' => []],
|
||||||
],
|
],
|
||||||
@@ -1450,7 +1450,7 @@ class ApplicationsController extends Controller
|
|||||||
], 404);
|
], 404);
|
||||||
}
|
}
|
||||||
$server = $application->destination->server;
|
$server = $application->destination->server;
|
||||||
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect'];
|
$allowedFields = ['name', 'description', 'is_static', 'domains', 'git_repository', 'git_branch', 'git_commit_sha', 'docker_registry_image_name', 'docker_registry_image_tag', 'build_pack', 'static_image', 'install_command', 'build_command', 'start_command', 'ports_exposes', 'ports_mappings', 'base_directory', 'publish_directory', 'health_check_enabled', 'health_check_path', 'health_check_port', 'health_check_host', 'health_check_method', 'health_check_return_code', 'health_check_scheme', 'health_check_response_text', 'health_check_interval', 'health_check_timeout', 'health_check_retries', 'health_check_start_period', 'limits_memory', 'limits_memory_swap', 'limits_memory_swappiness', 'limits_memory_reservation', 'limits_cpus', 'limits_cpuset', 'limits_cpu_shares', 'custom_labels', 'custom_docker_run_options', 'post_deployment_command', 'post_deployment_command_container', 'pre_deployment_command', 'pre_deployment_command_container', 'watch_paths', 'manual_webhook_secret_github', 'manual_webhook_secret_gitlab', 'manual_webhook_secret_bitbucket', 'manual_webhook_secret_gitea', 'docker_compose_location', 'docker_compose_raw', 'docker_compose_custom_start_command', 'docker_compose_custom_build_command', 'docker_compose_domains', 'redirect', 'instant_deploy'];
|
||||||
|
|
||||||
$validator = customApiValidator($request->all(), [
|
$validator = customApiValidator($request->all(), [
|
||||||
sharedDataApplications(),
|
sharedDataApplications(),
|
||||||
@@ -1526,6 +1526,10 @@ class ApplicationsController extends Controller
|
|||||||
}
|
}
|
||||||
$request->offsetUnset('docker_compose_domains');
|
$request->offsetUnset('docker_compose_domains');
|
||||||
}
|
}
|
||||||
|
$instantDeploy = $request->instant_deploy;
|
||||||
|
|
||||||
|
removeUnnecessaryFieldsFromRequest($request);
|
||||||
|
|
||||||
$data = $request->all();
|
$data = $request->all();
|
||||||
data_set($data, 'fqdn', $domains);
|
data_set($data, 'fqdn', $domains);
|
||||||
if ($dockerComposeDomainsJson->count() > 0) {
|
if ($dockerComposeDomainsJson->count() > 0) {
|
||||||
@@ -1534,6 +1538,16 @@ class ApplicationsController extends Controller
|
|||||||
$application->fill($data);
|
$application->fill($data);
|
||||||
$application->save();
|
$application->save();
|
||||||
|
|
||||||
|
if ($instantDeploy) {
|
||||||
|
$deployment_uuid = new Cuid2;
|
||||||
|
|
||||||
|
queue_application_deployment(
|
||||||
|
application: $application,
|
||||||
|
deployment_uuid: $deployment_uuid,
|
||||||
|
is_api: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'uuid' => $application->uuid,
|
'uuid' => $application->uuid,
|
||||||
]);
|
]);
|
||||||
|
@@ -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 ?string $coolify_variables = null;
|
||||||
|
|
||||||
private bool $preserveRepository = true;
|
private bool $preserveRepository = false;
|
||||||
|
|
||||||
public $tries = 1;
|
public $tries = 1;
|
||||||
|
|
||||||
@@ -198,9 +198,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
||||||
if ($this->application->settings->custom_internal_name && ! $this->application->settings->is_consistent_container_name_enabled) {
|
if ($this->application->settings->custom_internal_name && ! $this->application->settings->is_consistent_container_name_enabled) {
|
||||||
$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);
|
savePrivateKeyToFs($this->server);
|
||||||
$this->saved_outputs = collect();
|
$this->saved_outputs = collect();
|
||||||
@@ -276,6 +280,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->original_server = $this->server;
|
$this->original_server = $this->server;
|
||||||
} else {
|
} else {
|
||||||
$this->build_server = $buildServers->random();
|
$this->build_server = $buildServers->random();
|
||||||
|
$this->application_deployment_queue->build_server_id = $this->build_server->id;
|
||||||
$this->application_deployment_queue->addLogEntry("Found a suitable build server ({$this->build_server->name}).");
|
$this->application_deployment_queue->addLogEntry("Found a suitable build server ({$this->build_server->name}).");
|
||||||
$this->original_server = $this->server;
|
$this->original_server = $this->server;
|
||||||
$this->use_build_server = true;
|
$this->use_build_server = true;
|
||||||
@@ -414,15 +419,43 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->check_git_if_build_needed();
|
$this->check_git_if_build_needed();
|
||||||
$this->clone_repository();
|
$this->clone_repository();
|
||||||
|
if ($this->preserveRepository) {
|
||||||
|
foreach ($this->application->fileStorages as $fileStorage) {
|
||||||
|
$path = $fileStorage->fs_path;
|
||||||
|
$saveName = 'file_stat_'.$fileStorage->id;
|
||||||
|
$realPathInGit = str($path)->replace($this->application->workdir(), $this->workdir)->value();
|
||||||
|
// check if the file is a directory or a file inside the repository
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[executeInDocker($this->deployment_uuid, "stat -c '%F' {$realPathInGit}"), 'hidden' => true, 'ignore_errors' => true, 'save' => $saveName]
|
||||||
|
);
|
||||||
|
if ($this->saved_outputs->has($saveName)) {
|
||||||
|
$fileStat = $this->saved_outputs->get($saveName);
|
||||||
|
if ($fileStat->value() === 'directory' && ! $fileStorage->is_directory) {
|
||||||
|
$fileStorage->is_directory = true;
|
||||||
|
$fileStorage->content = null;
|
||||||
|
$fileStorage->save();
|
||||||
|
$fileStorage->deleteStorageOnServer();
|
||||||
|
$fileStorage->saveStorageOnServer();
|
||||||
|
} elseif ($fileStat->value() === 'regular file' && $fileStorage->is_directory) {
|
||||||
|
$fileStorage->is_directory = false;
|
||||||
|
$fileStorage->is_based_on_git = true;
|
||||||
|
$fileStorage->save();
|
||||||
|
$fileStorage->deleteStorageOnServer();
|
||||||
|
$fileStorage->saveStorageOnServer();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
$this->generate_image_names();
|
$this->generate_image_names();
|
||||||
$this->cleanup_git();
|
$this->cleanup_git();
|
||||||
$this->application->loadComposeFile(isInit: false);
|
$this->application->loadComposeFile(isInit: false);
|
||||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||||
$this->application->parseRawCompose();
|
$this->application->oldRawParser();
|
||||||
$yaml = $composeFile = $this->application->docker_compose_raw;
|
$yaml = $composeFile = $this->application->docker_compose_raw;
|
||||||
$this->save_environment_variables();
|
$this->save_environment_variables();
|
||||||
} else {
|
} else {
|
||||||
$composeFile = $this->application->parseCompose(pull_request_id: $this->pull_request_id, preview_id: data_get($this, 'preview.id'));
|
$composeFile = $this->application->oldParser(pull_request_id: $this->pull_request_id, preview_id: data_get($this->preview, 'id'));
|
||||||
$this->save_environment_variables();
|
$this->save_environment_variables();
|
||||||
if (! is_null($this->env_filename)) {
|
if (! is_null($this->env_filename)) {
|
||||||
$services = collect($composeFile['services']);
|
$services = collect($composeFile['services']);
|
||||||
@@ -439,7 +472,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$yaml = Yaml::dump($composeFile->toArray(), 10);
|
$yaml = Yaml::dump(convertToArray($composeFile), 10);
|
||||||
}
|
}
|
||||||
$this->docker_compose_base64 = base64_encode($yaml);
|
$this->docker_compose_base64 = base64_encode($yaml);
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
@@ -480,6 +513,7 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start compose file
|
// Start compose file
|
||||||
|
$server_workdir = $this->application->workdir();
|
||||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||||
if ($this->docker_compose_custom_start_command) {
|
if ($this->docker_compose_custom_start_command) {
|
||||||
$this->write_deployment_configurations();
|
$this->write_deployment_configurations();
|
||||||
@@ -488,7 +522,6 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->write_deployment_configurations();
|
$this->write_deployment_configurations();
|
||||||
$server_workdir = $this->application->workdir();
|
|
||||||
$this->docker_compose_location = '/docker-compose.yaml';
|
$this->docker_compose_location = '/docker-compose.yaml';
|
||||||
|
|
||||||
$command = "{$this->coolify_variables} docker compose";
|
$command = "{$this->coolify_variables} docker compose";
|
||||||
@@ -508,15 +541,27 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$command = "{$this->coolify_variables} docker compose";
|
$command = "{$this->coolify_variables} docker compose";
|
||||||
if ($this->env_filename) {
|
if ($this->preserveRepository) {
|
||||||
$command .= " --env-file {$this->workdir}/{$this->env_filename}";
|
if ($this->env_filename) {
|
||||||
}
|
$command .= " --env-file {$server_workdir}/{$this->env_filename}";
|
||||||
$command .= " --project-name {$this->application->uuid} --project-directory {$this->workdir} -f {$this->workdir}{$this->docker_compose_location} up -d";
|
}
|
||||||
|
$command .= " --project-name {$this->application->uuid} --project-directory {$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 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
[
|
[
|
||||||
"mkdir -p $this->configuration_dir",
|
"mkdir -p $this->configuration_dir",
|
||||||
],
|
],
|
||||||
// removing this now as we are using docker cp
|
|
||||||
// [
|
|
||||||
// "rm -rf $this->configuration_dir/{*,.*}",
|
|
||||||
// ],
|
|
||||||
[
|
[
|
||||||
"docker cp {$this->deployment_uuid}:{$this->workdir}/. {$this->configuration_dir}",
|
"docker cp {$this->deployment_uuid}:{$this->workdir}/. {$this->configuration_dir}",
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
foreach ($this->application->fileStorages as $fileStorage) {
|
||||||
|
if (! $fileStorage->is_based_on_git && ! $fileStorage->is_directory) {
|
||||||
|
$fileStorage->saveStorageOnServer();
|
||||||
|
}
|
||||||
|
}
|
||||||
if ($this->use_build_server) {
|
if ($this->use_build_server) {
|
||||||
$this->server = $this->build_server;
|
$this->server = $this->build_server;
|
||||||
}
|
}
|
||||||
@@ -851,11 +897,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', '');
|
$url = str($this->preview->fqdn)->replace('http://', '')->replace('https://', '');
|
||||||
$envs->push("COOLIFY_URL={$url}");
|
$envs->push("COOLIFY_URL={$url}");
|
||||||
}
|
}
|
||||||
if ($this->application->environment_variables_preview->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
if ($this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
||||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
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->environment_variables_preview->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
|
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
foreach ($sorted_environment_variables_preview as $env) {
|
foreach ($sorted_environment_variables_preview as $env) {
|
||||||
$real_value = $env->real_value;
|
$real_value = $env->real_value;
|
||||||
@@ -897,11 +945,13 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
|
$url = str($this->application->fqdn)->replace('http://', '')->replace('https://', '');
|
||||||
$envs->push("COOLIFY_URL={$url}");
|
$envs->push("COOLIFY_URL={$url}");
|
||||||
}
|
}
|
||||||
if ($this->application->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
if ($this->application->compose_parsing_version === '1' || $this->application->compose_parsing_version === '2') {
|
||||||
$envs->push("COOLIFY_BRANCH={$local_branch}");
|
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->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
|
$envs->push("COOLIFY_CONTAINER_NAME={$this->container_name}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
foreach ($sorted_environment_variables as $env) {
|
foreach ($sorted_environment_variables as $env) {
|
||||||
$real_value = $env->real_value;
|
$real_value = $env->real_value;
|
||||||
@@ -1708,13 +1758,20 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
|
if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
|
||||||
$docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array;
|
$docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (count($persistent_storages) > 0) {
|
if (count($persistent_storages) > 0) {
|
||||||
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_storages;
|
if (! data_get($docker_compose, 'services.'.$this->container_name.'.volumes')) {
|
||||||
|
$docker_compose['services'][$this->container_name]['volumes'] = [];
|
||||||
|
}
|
||||||
|
$docker_compose['services'][$this->container_name]['volumes'] = array_merge($docker_compose['services'][$this->container_name]['volumes'], $persistent_storages);
|
||||||
}
|
}
|
||||||
if (count($persistent_file_volumes) > 0) {
|
if (count($persistent_file_volumes) > 0) {
|
||||||
$docker_compose['services'][$this->container_name]['volumes'] = $persistent_file_volumes->map(function ($item) {
|
if (! data_get($docker_compose, 'services.'.$this->container_name.'.volumes')) {
|
||||||
|
$docker_compose['services'][$this->container_name]['volumes'] = [];
|
||||||
|
}
|
||||||
|
$docker_compose['services'][$this->container_name]['volumes'] = array_merge($docker_compose['services'][$this->container_name]['volumes'], $persistent_file_volumes->map(function ($item) {
|
||||||
return "$item->fs_path:$item->mount_path";
|
return "$item->fs_path:$item->mount_path";
|
||||||
})->toArray();
|
})->toArray());
|
||||||
}
|
}
|
||||||
if (count($volume_names) > 0) {
|
if (count($volume_names) > 0) {
|
||||||
$docker_compose['volumes'] = $volume_names;
|
$docker_compose['volumes'] = $volume_names;
|
||||||
@@ -2140,7 +2197,6 @@ COPY ./nginx.conf /etc/nginx/conf.d/default.conf");
|
|||||||
} else {
|
} else {
|
||||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
|
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
|
||||||
}
|
}
|
||||||
$dockerfile->splice(1, 0, "ARG {$env->key}={$env->real_value}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
|
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
|
||||||
|
@@ -56,6 +56,8 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public ?string $backup_output = null;
|
public ?string $backup_output = null;
|
||||||
|
|
||||||
|
public ?string $postgres_password = null;
|
||||||
|
|
||||||
public ?S3Storage $s3 = null;
|
public ?S3Storage $s3 = null;
|
||||||
|
|
||||||
public function __construct($backup)
|
public function __construct($backup)
|
||||||
@@ -89,8 +91,6 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
BackupCreated::dispatch($this->team->id);
|
|
||||||
|
|
||||||
// Check if team is exists
|
// Check if team is exists
|
||||||
if (is_null($this->team)) {
|
if (is_null($this->team)) {
|
||||||
$this->backup->update(['status' => 'failed']);
|
$this->backup->update(['status' => 'failed']);
|
||||||
@@ -99,6 +99,9 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BackupCreated::dispatch($this->team->id);
|
||||||
|
|
||||||
$status = str(data_get($this->database, 'status'));
|
$status = str(data_get($this->database, 'status'));
|
||||||
if (! $status->startsWith('running') && $this->database->id !== 0) {
|
if (! $status->startsWith('running') && $this->database->id !== 0) {
|
||||||
ray('database not running');
|
ray('database not running');
|
||||||
@@ -134,6 +137,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
} else {
|
} else {
|
||||||
$databasesToBackup = $this->database->postgres_user;
|
$databasesToBackup = $this->database->postgres_user;
|
||||||
}
|
}
|
||||||
|
$this->postgres_password = $envs->filter(function ($env) {
|
||||||
|
return str($env)->startsWith('POSTGRES_PASSWORD=');
|
||||||
|
})->first();
|
||||||
|
if ($this->postgres_password) {
|
||||||
|
$this->postgres_password = str($this->postgres_password)->after('POSTGRES_PASSWORD=')->value();
|
||||||
|
}
|
||||||
|
|
||||||
} elseif (str($databaseType)->contains('mysql')) {
|
} elseif (str($databaseType)->contains('mysql')) {
|
||||||
$this->container_name = "{$this->database->name}-$serviceUuid";
|
$this->container_name = "{$this->database->name}-$serviceUuid";
|
||||||
$this->directory_name = $serviceName.'-'.$this->container_name;
|
$this->directory_name = $serviceName.'-'.$this->container_name;
|
||||||
@@ -336,7 +346,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
$url = $this->database->internal_db_url;
|
$url = $this->database->internal_db_url;
|
||||||
if ($databaseWithCollections === 'all') {
|
if ($databaseWithCollections === 'all') {
|
||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||||
if (str($this->database->image)->startsWith('mongo:4.0')) {
|
if (str($this->database->image)->startsWith('mongo:4')) {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
|
||||||
} else {
|
} else {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --gzip --archive > $this->backup_location";
|
||||||
@@ -351,13 +361,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
}
|
}
|
||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||||
if ($collectionsToExclude->count() === 0) {
|
if ($collectionsToExclude->count() === 0) {
|
||||||
if (str($this->database->image)->startsWith('mongo:4.0')) {
|
if (str($this->database->image)->startsWith('mongo:4')) {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --archive > $this->backup_location";
|
||||||
} else {
|
} else {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --archive > $this->backup_location";
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (str($this->database->image)->startsWith('mongo:4.0')) {
|
if (str($this->database->image)->startsWith('mongo:4')) {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --uri=$url --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
|
||||||
} else {
|
} else {
|
||||||
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
|
$commands[] = "docker exec $this->container_name mongodump --authenticationDatabase=admin --uri=$url --db $databaseName --gzip --excludeCollection ".$collectionsToExclude->implode(' --excludeCollection ')." --archive > $this->backup_location";
|
||||||
@@ -381,7 +391,13 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$commands[] = 'mkdir -p '.$this->backup_dir;
|
$commands[] = 'mkdir -p '.$this->backup_dir;
|
||||||
$commands[] = "docker exec $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
|
$backupCommand = 'docker exec';
|
||||||
|
if ($this->postgres_password) {
|
||||||
|
$backupCommand .= " -e PGPASSWORD=$this->postgres_password";
|
||||||
|
}
|
||||||
|
$backupCommand .= " $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
|
||||||
|
|
||||||
|
$commands[] = $backupCommand;
|
||||||
$this->backup_output = instant_remote_process($commands, $this->server);
|
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||||
$this->backup_output = trim($this->backup_output);
|
$this->backup_output = trim($this->backup_output);
|
||||||
if ($this->backup_output === '') {
|
if ($this->backup_output === '') {
|
||||||
@@ -452,7 +468,7 @@ class DatabaseBackupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
if ($this->backup->number_of_backups_locally === 0) {
|
if ($this->backup->number_of_backups_locally === 0) {
|
||||||
$deletable = $this->backup->executions()->where('status', 'success');
|
$deletable = $this->backup->executions()->where('status', 'success');
|
||||||
} else {
|
} else {
|
||||||
$deletable = $this->backup->executions()->where('status', 'success')->orderByDesc('created_at')->skip($this->backup->number_of_backups_locally - 1);
|
$deletable = $this->backup->executions()->where('status', 'success')->skip($this->backup->number_of_backups_locally - 1);
|
||||||
}
|
}
|
||||||
foreach ($deletable->get() as $execution) {
|
foreach ($deletable->get() as $execution) {
|
||||||
delete_backup_locally($execution->filename, $this->server);
|
delete_backup_locally($execution->filename, $this->server);
|
||||||
|
@@ -19,7 +19,9 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
|
|
||||||
public $timeout = 300;
|
public $timeout = 300;
|
||||||
|
|
||||||
public int|string|null $usageBefore = null;
|
public $tries = 2;
|
||||||
|
|
||||||
|
public ?string $usageBefore = null;
|
||||||
|
|
||||||
public function __construct(public Server $server) {}
|
public function __construct(public Server $server) {}
|
||||||
|
|
||||||
@@ -36,6 +38,10 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
$this->usageBefore = $this->server->getDiskUsage();
|
$this->usageBefore = $this->server->getDiskUsage();
|
||||||
if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
|
if (str($this->usageBefore)->isEmpty() || $this->usageBefore === null || $this->usageBefore === 0) {
|
||||||
Log::info('DockerCleanupJob force cleanup on ' . $this->server->name);
|
Log::info('DockerCleanupJob force cleanup on ' . $this->server->name);
|
||||||
@@ -44,7 +50,7 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
||||||
CleanupDocker::run(server: $this->server, force: false);
|
CleanupDocker::run(server: $this->server);
|
||||||
$usageAfter = $this->server->getDiskUsage();
|
$usageAfter = $this->server->getDiskUsage();
|
||||||
if ($usageAfter < $this->usageBefore) {
|
if ($usageAfter < $this->usageBefore) {
|
||||||
$this->server->team?->notify(new DockerCleanup($this->server, 'Saved ' . ($this->usageBefore - $usageAfter) . '% disk space.'));
|
$this->server->team?->notify(new DockerCleanup($this->server, 'Saved ' . ($this->usageBefore - $usageAfter) . '% disk space.'));
|
||||||
@@ -56,7 +62,8 @@ class DockerCleanupJob implements ShouldBeEncrypted, ShouldQueue
|
|||||||
Log::info('No need to clean up ' . $this->server->name);
|
Log::info('No need to clean up ' . $this->server->name);
|
||||||
}
|
}
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
ray($e->getMessage());
|
CleanupDocker::run(server: $this->server);
|
||||||
|
Log::error('DockerCleanupJob failed: '.$e->getMessage());
|
||||||
throw $e;
|
throw $e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -66,9 +66,9 @@ class Advanced extends Component
|
|||||||
$this->dispatch('resetDefaultLabels', false);
|
$this->dispatch('resetDefaultLabels', false);
|
||||||
}
|
}
|
||||||
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
if ($this->application->settings->is_raw_compose_deployment_enabled) {
|
||||||
$this->application->parseRawCompose();
|
$this->application->oldRawParser();
|
||||||
} else {
|
} else {
|
||||||
$this->application->parseCompose();
|
$this->application->oldParser();
|
||||||
}
|
}
|
||||||
$this->application->settings->save();
|
$this->application->settings->save();
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
@@ -96,6 +96,12 @@ class Advanced extends Component
|
|||||||
} else {
|
} else {
|
||||||
$this->application->settings->custom_internal_name = null;
|
$this->application->settings->custom_internal_name = null;
|
||||||
}
|
}
|
||||||
|
if (is_null($this->application->settings->custom_internal_name)) {
|
||||||
|
$this->application->settings->save();
|
||||||
|
$this->dispatch('success', 'Custom name saved.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
$customInternalName = $this->application->settings->custom_internal_name;
|
$customInternalName = $this->application->settings->custom_internal_name;
|
||||||
$server = $this->application->destination->server;
|
$server = $this->application->destination->server;
|
||||||
$allApplications = $server->applications();
|
$allApplications = $server->applications();
|
||||||
|
@@ -55,9 +55,14 @@ class DeploymentNavbar extends Component
|
|||||||
public function cancel()
|
public function cancel()
|
||||||
{
|
{
|
||||||
$kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}";
|
$kill_command = "docker rm -f {$this->application_deployment_queue->deployment_uuid}";
|
||||||
|
$build_server_id = $this->application_deployment_queue->build_server_id;
|
||||||
$server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id;
|
$server_id = $this->application_deployment_queue->server_id ?? $this->application->destination->server_id;
|
||||||
try {
|
try {
|
||||||
$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) {
|
if ($this->application_deployment_queue->logs) {
|
||||||
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
$previous_logs = json_decode($this->application_deployment_queue->logs, associative: true, flags: JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
@@ -3,8 +3,8 @@
|
|||||||
namespace App\Livewire\Project\Application;
|
namespace App\Livewire\Project\Application;
|
||||||
|
|
||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\LocalFileVolume;
|
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
@@ -30,6 +30,8 @@ class General extends Component
|
|||||||
|
|
||||||
public ?string $ports_exposes = null;
|
public ?string $ports_exposes = null;
|
||||||
|
|
||||||
|
public bool $is_preserve_repository_enabled = false;
|
||||||
|
|
||||||
public bool $is_container_label_escape_enabled = true;
|
public bool $is_container_label_escape_enabled = true;
|
||||||
|
|
||||||
public $customLabels;
|
public $customLabels;
|
||||||
@@ -130,7 +132,7 @@ class General extends Component
|
|||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->parsedServices = $this->application->parseCompose();
|
$this->parsedServices = $this->application->oldParser();
|
||||||
if (is_null($this->parsedServices) || empty($this->parsedServices)) {
|
if (is_null($this->parsedServices) || empty($this->parsedServices)) {
|
||||||
$this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
|
$this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
|
||||||
|
|
||||||
@@ -145,6 +147,7 @@ class General extends Component
|
|||||||
}
|
}
|
||||||
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
|
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
|
||||||
$this->ports_exposes = $this->application->ports_exposes;
|
$this->ports_exposes = $this->application->ports_exposes;
|
||||||
|
$this->is_preserve_repository_enabled = $this->application->settings->is_preserve_repository_enabled;
|
||||||
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
$this->is_container_label_escape_enabled = $this->application->settings->is_container_label_escape_enabled;
|
||||||
$this->customLabels = $this->application->parseContainerLabels();
|
$this->customLabels = $this->application->parseContainerLabels();
|
||||||
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
|
if (! $this->customLabels && $this->application->destination->server->proxyType() !== 'NONE' && ! $this->application->settings->is_container_label_readonly_enabled) {
|
||||||
@@ -168,9 +171,21 @@ class General extends Component
|
|||||||
$this->application->settings->save();
|
$this->application->settings->save();
|
||||||
$this->dispatch('success', 'Settings saved.');
|
$this->dispatch('success', 'Settings saved.');
|
||||||
$this->application->refresh();
|
$this->application->refresh();
|
||||||
|
|
||||||
|
// If port_exposes changed, reset default labels
|
||||||
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
|
if ($this->ports_exposes !== $this->application->ports_exposes || $this->is_container_label_escape_enabled !== $this->application->settings->is_container_label_escape_enabled) {
|
||||||
$this->resetDefaultLabels(false);
|
$this->resetDefaultLabels(false);
|
||||||
}
|
}
|
||||||
|
if ($this->is_preserve_repository_enabled !== $this->application->settings->is_preserve_repository_enabled) {
|
||||||
|
if ($this->application->settings->is_preserve_repository_enabled === false) {
|
||||||
|
$this->application->fileStorages->each(function ($storage) {
|
||||||
|
$storage->is_based_on_git = $this->application->settings->is_preserve_repository_enabled;
|
||||||
|
$storage->save();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadComposeFile($isInit = false)
|
public function loadComposeFile($isInit = false)
|
||||||
@@ -179,39 +194,18 @@ class General extends Component
|
|||||||
if ($isInit && $this->application->docker_compose_raw) {
|
if ($isInit && $this->application->docker_compose_raw) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Must reload the application to get the latest database changes
|
||||||
|
// Why? Not sure, but it works.
|
||||||
|
$this->application->refresh();
|
||||||
|
|
||||||
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit);
|
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation] = $this->application->loadComposeFile($isInit);
|
||||||
if (is_null($this->parsedServices)) {
|
if (is_null($this->parsedServices)) {
|
||||||
$this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
|
$this->dispatch('error', 'Failed to parse your docker-compose file. Please check the syntax and try again.');
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$compose = $this->application->parseCompose();
|
$this->application->oldParser();
|
||||||
$services = data_get($compose, 'services');
|
|
||||||
if ($services) {
|
|
||||||
$volumes = collect($services)->map(function ($service) {
|
|
||||||
return data_get($service, 'volumes');
|
|
||||||
})->flatten()->filter(function ($volume) {
|
|
||||||
return str($volume)->startsWith('/data/coolify');
|
|
||||||
})->unique()->values();
|
|
||||||
foreach ($volumes as $volume) {
|
|
||||||
$source = str($volume)->before(':');
|
|
||||||
$target = str($volume)->after(':')->beforeLast(':');
|
|
||||||
|
|
||||||
LocalFileVolume::updateOrCreate(
|
|
||||||
[
|
|
||||||
'mount_path' => $target,
|
|
||||||
'resource_id' => $this->application->id,
|
|
||||||
'resource_type' => get_class($this->application),
|
|
||||||
],
|
|
||||||
[
|
|
||||||
'fs_path' => $source,
|
|
||||||
'mount_path' => $target,
|
|
||||||
'resource_id' => $this->application->id,
|
|
||||||
'resource_type' => get_class($this->application),
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->dispatch('success', 'Docker compose file loaded.');
|
$this->dispatch('success', 'Docker compose file loaded.');
|
||||||
$this->dispatch('compose_loaded');
|
$this->dispatch('compose_loaded');
|
||||||
$this->dispatch('refreshStorages');
|
$this->dispatch('refreshStorages');
|
||||||
|
@@ -58,7 +58,7 @@ class BackupExecutions extends Component
|
|||||||
public function refreshBackupExecutions(): void
|
public function refreshBackupExecutions(): void
|
||||||
{
|
{
|
||||||
if ($this->backup) {
|
if ($this->backup) {
|
||||||
$this->executions = $this->backup->executions()->get()->sortBy('created_at');
|
$this->executions = $this->backup->executions()->get();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -31,6 +31,7 @@ class General extends Component
|
|||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -42,6 +43,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -54,7 +56,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (!$this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
@@ -71,14 +73,14 @@ class General extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database->is_public && ! $this->database->public_port) {
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
$this->dispatch('error', 'Public port is required.');
|
$this->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
if (! str($this->database->status)->startsWith('running')) {
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -93,7 +95,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
@@ -30,6 +30,7 @@ class General extends Component
|
|||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -40,6 +41,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -52,7 +54,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (!$this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
@@ -86,14 +88,14 @@ class General extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database->is_public && ! $this->database->public_port) {
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
$this->dispatch('error', 'Public port is required.');
|
$this->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
if (! str($this->database->status)->startsWith('running')) {
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -108,7 +110,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
@@ -31,6 +31,7 @@ class General extends Component
|
|||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -42,6 +43,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -55,7 +57,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (!$this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
@@ -92,14 +94,14 @@ class General extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database->is_public && ! $this->database->public_port) {
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
$this->dispatch('error', 'Public port is required.');
|
$this->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
if (! str($this->database->status)->startsWith('running')) {
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -114,7 +116,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
@@ -34,6 +34,7 @@ class General extends Component
|
|||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -48,6 +49,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -61,7 +63,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (!$this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
@@ -98,14 +100,14 @@ class General extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database->is_public && ! $this->database->public_port) {
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
$this->dispatch('error', 'Public port is required.');
|
$this->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
if (! str($this->database->status)->startsWith('running')) {
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -120,7 +122,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
@@ -33,6 +33,7 @@ class General extends Component
|
|||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -46,6 +47,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -59,7 +61,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (!$this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
@@ -99,14 +101,14 @@ class General extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database->is_public && ! $this->database->public_port) {
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
$this->dispatch('error', 'Public port is required.');
|
$this->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
if (! str($this->database->status)->startsWith('running')) {
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -121,7 +123,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
@@ -34,6 +34,7 @@ class General extends Component
|
|||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -48,6 +49,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -60,7 +62,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (!$this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
@@ -97,14 +99,14 @@ class General extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database->is_public && ! $this->database->public_port) {
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
$this->dispatch('error', 'Public port is required.');
|
$this->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
if (! str($this->database->status)->startsWith('running')) {
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -119,7 +121,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
@@ -49,6 +49,7 @@ class General extends Component
|
|||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -65,6 +66,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
|
@@ -31,6 +31,7 @@ class General extends Component
|
|||||||
'database.is_public' => 'nullable|boolean',
|
'database.is_public' => 'nullable|boolean',
|
||||||
'database.public_port' => 'nullable|integer',
|
'database.public_port' => 'nullable|integer',
|
||||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||||
|
'database.custom_docker_run_options' => 'nullable',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $validationAttributes = [
|
protected $validationAttributes = [
|
||||||
@@ -42,6 +43,7 @@ class General extends Component
|
|||||||
'database.ports_mappings' => 'Port Mapping',
|
'database.ports_mappings' => 'Port Mapping',
|
||||||
'database.is_public' => 'Is Public',
|
'database.is_public' => 'Is Public',
|
||||||
'database.public_port' => 'Public Port',
|
'database.public_port' => 'Public Port',
|
||||||
|
'database.custom_docker_run_options' => 'Custom Docker Options',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -55,7 +57,7 @@ class General extends Component
|
|||||||
public function instantSaveAdvanced()
|
public function instantSaveAdvanced()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (! $this->server->isLogDrainEnabled()) {
|
if (!$this->server->isLogDrainEnabled()) {
|
||||||
$this->database->is_log_drain_enabled = false;
|
$this->database->is_log_drain_enabled = false;
|
||||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||||
|
|
||||||
@@ -86,14 +88,14 @@ class General extends Component
|
|||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if ($this->database->is_public && ! $this->database->public_port) {
|
if ($this->database->is_public && !$this->database->public_port) {
|
||||||
$this->dispatch('error', 'Public port is required.');
|
$this->dispatch('error', 'Public port is required.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if ($this->database->is_public) {
|
if ($this->database->is_public) {
|
||||||
if (! str($this->database->status)->startsWith('running')) {
|
if (!str($this->database->status)->startsWith('running')) {
|
||||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||||
$this->database->is_public = false;
|
$this->database->is_public = false;
|
||||||
|
|
||||||
@@ -108,7 +110,7 @@ class General extends Component
|
|||||||
$this->db_url_public = $this->database->external_db_url;
|
$this->db_url_public = $this->database->external_db_url;
|
||||||
$this->database->save();
|
$this->database->save();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
$this->database->is_public = ! $this->database->is_public;
|
$this->database->is_public = !$this->database->is_public;
|
||||||
|
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
}
|
}
|
||||||
|
@@ -5,6 +5,8 @@ namespace App\Livewire\Project\New;
|
|||||||
use App\Models\EnvironmentVariable;
|
use App\Models\EnvironmentVariable;
|
||||||
use App\Models\Project;
|
use App\Models\Project;
|
||||||
use App\Models\Service;
|
use App\Models\Service;
|
||||||
|
use App\Models\StandaloneDocker;
|
||||||
|
use App\Models\SwarmDocker;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
@@ -58,12 +60,26 @@ class DockerCompose extends Component
|
|||||||
|
|
||||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
||||||
|
|
||||||
|
$destination_uuid = $this->query['destination'];
|
||||||
|
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||||
|
if (! $destination) {
|
||||||
|
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
|
||||||
|
}
|
||||||
|
if (! $destination) {
|
||||||
|
throw new \Exception('Destination not found. What?!');
|
||||||
|
}
|
||||||
|
$destination_class = $destination->getMorphClass();
|
||||||
|
|
||||||
$service = Service::create([
|
$service = Service::create([
|
||||||
'name' => 'service'.Str::random(10),
|
'name' => 'service'.Str::random(10),
|
||||||
'docker_compose_raw' => $this->dockerComposeRaw,
|
'docker_compose_raw' => $this->dockerComposeRaw,
|
||||||
'environment_id' => $environment->id,
|
'environment_id' => $environment->id,
|
||||||
'server_id' => (int) $server_id,
|
'server_id' => (int) $server_id,
|
||||||
|
'destination_id' => $destination->id,
|
||||||
|
'destination_type' => $destination_class,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$variables = parseEnvFormatToArray($this->envFile);
|
$variables = parseEnvFormatToArray($this->envFile);
|
||||||
foreach ($variables as $key => $variable) {
|
foreach ($variables as $key => $variable) {
|
||||||
EnvironmentVariable::create([
|
EnvironmentVariable::create([
|
||||||
|
@@ -45,6 +45,8 @@ class Select extends Component
|
|||||||
|
|
||||||
public ?string $selectedEnvironment = null;
|
public ?string $selectedEnvironment = null;
|
||||||
|
|
||||||
|
public string $postgresql_type = 'postgres:16-alpine';
|
||||||
|
|
||||||
public ?string $existingPostgresqlUrl = null;
|
public ?string $existingPostgresqlUrl = null;
|
||||||
|
|
||||||
public ?string $search = null;
|
public ?string $search = null;
|
||||||
@@ -202,6 +204,8 @@ class Select extends Component
|
|||||||
$docker = $this->standaloneDockers->first() ?? $this->swarmDockers->first();
|
$docker = $this->standaloneDockers->first() ?? $this->swarmDockers->first();
|
||||||
if ($docker) {
|
if ($docker) {
|
||||||
$this->setDestination($docker->uuid);
|
$this->setDestination($docker->uuid);
|
||||||
|
|
||||||
|
return $this->whatToDoNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->current_step = 'destinations';
|
$this->current_step = 'destinations';
|
||||||
@@ -211,15 +215,38 @@ class Select extends Component
|
|||||||
{
|
{
|
||||||
$this->destination_uuid = $destination_uuid;
|
$this->destination_uuid = $destination_uuid;
|
||||||
|
|
||||||
|
return $this->whatToDoNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setPostgresqlType(string $type)
|
||||||
|
{
|
||||||
|
$this->postgresql_type = $type;
|
||||||
|
|
||||||
return redirect()->route('project.resource.create', [
|
return redirect()->route('project.resource.create', [
|
||||||
'project_uuid' => $this->parameters['project_uuid'],
|
'project_uuid' => $this->parameters['project_uuid'],
|
||||||
'environment_name' => $this->parameters['environment_name'],
|
'environment_name' => $this->parameters['environment_name'],
|
||||||
'type' => $this->type,
|
'type' => $this->type,
|
||||||
'destination' => $this->destination_uuid,
|
'destination' => $this->destination_uuid,
|
||||||
'server_id' => $this->server_id,
|
'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()
|
public function loadServers()
|
||||||
{
|
{
|
||||||
$this->servers = Server::isUsable()->get();
|
$this->servers = Server::isUsable()->get();
|
||||||
|
@@ -18,6 +18,7 @@ class Create extends Component
|
|||||||
$type = str(request()->query('type'));
|
$type = str(request()->query('type'));
|
||||||
$destination_uuid = request()->query('destination');
|
$destination_uuid = request()->query('destination');
|
||||||
$server_id = request()->query('server_id');
|
$server_id = request()->query('server_id');
|
||||||
|
$database_image = request()->query('database_image');
|
||||||
|
|
||||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||||
if (! $project) {
|
if (! $project) {
|
||||||
@@ -33,7 +34,11 @@ class Create extends Component
|
|||||||
|
|
||||||
if (in_array($type, DATABASE_TYPES)) {
|
if (in_array($type, DATABASE_TYPES)) {
|
||||||
if ($type->value() === 'postgresql') {
|
if ($type->value() === 'postgresql') {
|
||||||
$database = create_standalone_postgresql($environment->id, $destination_uuid);
|
$database = create_standalone_postgresql(
|
||||||
|
environmentId: $environment->id,
|
||||||
|
destinationUuid: $destination_uuid,
|
||||||
|
databaseImage: $database_image
|
||||||
|
);
|
||||||
} elseif ($type->value() === 'redis') {
|
} elseif ($type->value() === 'redis') {
|
||||||
$database = create_standalone_redis($environment->id, $destination_uuid);
|
$database = create_standalone_redis($environment->id, $destination_uuid);
|
||||||
} elseif ($type->value() === 'mongodb') {
|
} elseif ($type->value() === 'mongodb') {
|
||||||
|
@@ -25,6 +25,7 @@ class Configuration extends Component
|
|||||||
return [
|
return [
|
||||||
"echo-private:user.{$userId},ServiceStatusChanged" => 'check_status',
|
"echo-private:user.{$userId},ServiceStatusChanged" => 'check_status',
|
||||||
'check_status',
|
'check_status',
|
||||||
|
'refresh' => '$refresh',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -11,7 +11,11 @@ class EditCompose extends Component
|
|||||||
|
|
||||||
public $serviceId;
|
public $serviceId;
|
||||||
|
|
||||||
protected $listeners = ['refreshEnvs', 'envsUpdated'];
|
protected $listeners = [
|
||||||
|
'refreshEnvs',
|
||||||
|
'envsUpdated',
|
||||||
|
'refresh' => 'envsUpdated',
|
||||||
|
];
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
'service.docker_compose_raw' => 'required',
|
'service.docker_compose_raw' => 'required',
|
||||||
|
@@ -33,6 +33,7 @@ class FileStorage extends Component
|
|||||||
'fileStorage.fs_path' => 'required',
|
'fileStorage.fs_path' => 'required',
|
||||||
'fileStorage.mount_path' => 'required',
|
'fileStorage.mount_path' => 'required',
|
||||||
'fileStorage.content' => 'nullable',
|
'fileStorage.content' => 'nullable',
|
||||||
|
'fileStorage.is_based_on_git' => 'required|boolean',
|
||||||
];
|
];
|
||||||
|
|
||||||
public function mount()
|
public function mount()
|
||||||
@@ -45,6 +46,7 @@ class FileStorage extends Component
|
|||||||
$this->workdir = null;
|
$this->workdir = null;
|
||||||
$this->fs_path = $this->fileStorage->fs_path;
|
$this->fs_path = $this->fileStorage->fs_path;
|
||||||
}
|
}
|
||||||
|
$this->fileStorage->loadStorageOnServer();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function convertToDirectory()
|
public function convertToDirectory()
|
||||||
@@ -53,6 +55,7 @@ class FileStorage extends Component
|
|||||||
$this->fileStorage->deleteStorageOnServer();
|
$this->fileStorage->deleteStorageOnServer();
|
||||||
$this->fileStorage->is_directory = true;
|
$this->fileStorage->is_directory = true;
|
||||||
$this->fileStorage->content = null;
|
$this->fileStorage->content = null;
|
||||||
|
$this->fileStorage->is_based_on_git = false;
|
||||||
$this->fileStorage->save();
|
$this->fileStorage->save();
|
||||||
$this->fileStorage->saveStorageOnServer();
|
$this->fileStorage->saveStorageOnServer();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
@@ -68,6 +71,9 @@ class FileStorage extends Component
|
|||||||
$this->fileStorage->deleteStorageOnServer();
|
$this->fileStorage->deleteStorageOnServer();
|
||||||
$this->fileStorage->is_directory = false;
|
$this->fileStorage->is_directory = false;
|
||||||
$this->fileStorage->content = null;
|
$this->fileStorage->content = null;
|
||||||
|
if (data_get($this->resource, 'settings.is_preserve_repository_enabled')) {
|
||||||
|
$this->fileStorage->is_based_on_git = true;
|
||||||
|
}
|
||||||
$this->fileStorage->save();
|
$this->fileStorage->save();
|
||||||
$this->fileStorage->saveStorageOnServer();
|
$this->fileStorage->saveStorageOnServer();
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
|
@@ -53,7 +53,7 @@ class StackForm extends Component
|
|||||||
public function saveCompose($raw)
|
public function saveCompose($raw)
|
||||||
{
|
{
|
||||||
$this->service->docker_compose_raw = $raw;
|
$this->service->docker_compose_raw = $raw;
|
||||||
$this->submit();
|
$this->submit(notify: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
@@ -62,7 +62,7 @@ class StackForm extends Component
|
|||||||
$this->dispatch('success', 'Service settings saved.');
|
$this->dispatch('success', 'Service settings saved.');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function submit()
|
public function submit($notify = true)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->validate();
|
$this->validate();
|
||||||
@@ -76,7 +76,7 @@ class StackForm extends Component
|
|||||||
$this->service->refresh();
|
$this->service->refresh();
|
||||||
$this->service->saveComposeConfigs();
|
$this->service->saveComposeConfigs();
|
||||||
$this->dispatch('refreshEnvs');
|
$this->dispatch('refreshEnvs');
|
||||||
$this->dispatch('success', 'Service saved.');
|
$notify && $this->dispatch('success', 'Service saved.');
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
} finally {
|
} finally {
|
||||||
|
@@ -23,8 +23,8 @@ class All extends Component
|
|||||||
public string $view = 'normal';
|
public string $view = 'normal';
|
||||||
|
|
||||||
protected $listeners = [
|
protected $listeners = [
|
||||||
'refreshEnvs',
|
|
||||||
'saveKey' => 'submit',
|
'saveKey' => 'submit',
|
||||||
|
'environmentVariableDeleted' => 'refreshEnvs',
|
||||||
];
|
];
|
||||||
|
|
||||||
protected $rules = [
|
protected $rules = [
|
||||||
@@ -40,220 +40,240 @@ class All extends Component
|
|||||||
$this->showPreview = true;
|
$this->showPreview = true;
|
||||||
}
|
}
|
||||||
$this->modalId = new Cuid2;
|
$this->modalId = new Cuid2;
|
||||||
$this->sortMe();
|
$this->sortEnvironmentVariables();
|
||||||
$this->getDevView();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function sortMe()
|
|
||||||
{
|
|
||||||
if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose') {
|
|
||||||
if ($this->resource->settings->is_env_sorting_enabled) {
|
|
||||||
$this->resource->environment_variables = $this->resource->environment_variables->sortBy('key');
|
|
||||||
$this->resource->environment_variables_preview = $this->resource->environment_variables_preview->sortBy('key');
|
|
||||||
} else {
|
|
||||||
$this->resource->environment_variables = $this->resource->environment_variables->sortBy('id');
|
|
||||||
$this->resource->environment_variables_preview = $this->resource->environment_variables_preview->sortBy('id');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$this->getDevView();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function instantSave()
|
public function instantSave()
|
||||||
{
|
{
|
||||||
if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose') {
|
$this->resource->settings->save();
|
||||||
$this->resource->settings->save();
|
$this->sortEnvironmentVariables();
|
||||||
$this->dispatch('success', 'Environment variable settings updated.');
|
$this->dispatch('success', 'Environment variable settings updated.');
|
||||||
$this->sortMe();
|
}
|
||||||
|
|
||||||
|
public function sortEnvironmentVariables()
|
||||||
|
{
|
||||||
|
if ($this->resource->type() === 'application') {
|
||||||
|
$this->resource->load(['environment_variables', 'environment_variables_preview']);
|
||||||
|
} else {
|
||||||
|
$this->resource->load(['environment_variables']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$sortBy = data_get($this->resource, 'settings.is_env_sorting_enabled') ? 'key' : 'order';
|
||||||
|
|
||||||
|
$sortFunction = function ($variables) use ($sortBy) {
|
||||||
|
if (!$variables) {
|
||||||
|
return $variables;
|
||||||
|
}
|
||||||
|
if ($sortBy === 'key') {
|
||||||
|
return $variables->sortBy(function ($item) {
|
||||||
|
return strtolower($item->key);
|
||||||
|
}, SORT_NATURAL | SORT_FLAG_CASE)->values();
|
||||||
|
} else {
|
||||||
|
return $variables->sortBy('order')->values();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
$this->resource->environment_variables = $sortFunction($this->resource->environment_variables);
|
||||||
|
$this->resource->environment_variables_preview = $sortFunction($this->resource->environment_variables_preview);
|
||||||
|
|
||||||
|
$this->getDevView();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDevView()
|
public function getDevView()
|
||||||
{
|
{
|
||||||
$this->variables = $this->resource->environment_variables->map(function ($item) {
|
$this->variables = $this->formatEnvironmentVariables($this->resource->environment_variables);
|
||||||
|
if ($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) {
|
if ($item->is_shown_once) {
|
||||||
return "$item->key=(locked secret)";
|
return "$item->key=(Locked Secret, delete and add again to change)";
|
||||||
}
|
}
|
||||||
if ($item->is_multiline) {
|
if ($item->is_multiline) {
|
||||||
return "$item->key=(multiline, edit in normal view)";
|
return "$item->key=(Multiline environment variable, edit in normal view)";
|
||||||
}
|
}
|
||||||
|
|
||||||
return "$item->key=$item->value";
|
return "$item->key=$item->value";
|
||||||
})->join('
|
})->join("\n");
|
||||||
');
|
|
||||||
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('
|
|
||||||
');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function switch()
|
public function switch()
|
||||||
{
|
{
|
||||||
if ($this->view === 'normal') {
|
$this->view = $this->view === 'normal' ? 'dev' : 'normal';
|
||||||
$this->view = 'dev';
|
$this->sortEnvironmentVariables();
|
||||||
} else {
|
|
||||||
$this->view = 'normal';
|
|
||||||
}
|
|
||||||
$this->sortMe();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function saveVariables($isPreview)
|
public function submit($data = null)
|
||||||
{
|
{
|
||||||
if ($isPreview) {
|
try {
|
||||||
$variables = parseEnvFormatToArray($this->variablesPreview);
|
if ($data === null) {
|
||||||
$this->resource->environment_variables_preview()->whereNotIn('key', array_keys($variables))->delete();
|
$this->handleBulkSubmit();
|
||||||
} else {
|
|
||||||
$variables = parseEnvFormatToArray($this->variables);
|
|
||||||
$this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->delete();
|
|
||||||
}
|
|
||||||
foreach ($variables as $key => $variable) {
|
|
||||||
if ($isPreview) {
|
|
||||||
$found = $this->resource->environment_variables_preview()->where('key', $key)->first();
|
|
||||||
} else {
|
} else {
|
||||||
$found = $this->resource->environment_variables()->where('key', $key)->first();
|
$this->handleSingleSubmit($data);
|
||||||
}
|
}
|
||||||
if ($found) {
|
|
||||||
if ($found->is_shown_once || $found->is_multiline) {
|
$this->updateOrder();
|
||||||
continue;
|
$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;
|
$order++;
|
||||||
// if (str($found->value)->startsWith('{{') && str($found->value)->endsWith('}}')) {
|
}
|
||||||
// $type = str($found->value)->after('{{')->before('.')->value;
|
}
|
||||||
// if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
}
|
||||||
// $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
|
|
||||||
|
|
||||||
// return;
|
private function handleBulkSubmit()
|
||||||
// }
|
{
|
||||||
// }
|
$variables = parseEnvFormatToArray($this->variables);
|
||||||
$found->save();
|
$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 {
|
} else {
|
||||||
$environment = new EnvironmentVariable;
|
$environment = new EnvironmentVariable;
|
||||||
$environment->key = $key;
|
$environment->key = $key;
|
||||||
$environment->value = $variable;
|
$environment->value = $value;
|
||||||
// if (str($environment->value)->startsWith('{{') && str($environment->value)->endsWith('}}')) {
|
|
||||||
// $type = str($environment->value)->after('{{')->before('.')->value;
|
|
||||||
// if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
|
||||||
// $this->dispatch('error', 'Invalid shared variable type.', 'Valid types are: team, project, environment.');
|
|
||||||
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
$environment->is_build_time = false;
|
$environment->is_build_time = false;
|
||||||
$environment->is_multiline = false;
|
$environment->is_multiline = false;
|
||||||
$environment->is_preview = $isPreview ? true : false;
|
$environment->is_preview = $isPreview;
|
||||||
switch ($this->resource->type()) {
|
|
||||||
case 'application':
|
$this->setEnvironmentResourceId($environment);
|
||||||
$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();
|
$environment->save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($isPreview) {
|
}
|
||||||
$this->dispatch('success', 'Preview environment variables updated.');
|
|
||||||
} else {
|
private function setEnvironmentResourceId($environment)
|
||||||
$this->dispatch('success', 'Environment variables updated.');
|
{
|
||||||
|
$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()
|
public function refreshEnvs()
|
||||||
{
|
{
|
||||||
$this->resource->refresh();
|
$this->resource->refresh();
|
||||||
|
$this->sortEnvironmentVariables();
|
||||||
$this->getDevView();
|
$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;
|
public string $type;
|
||||||
|
|
||||||
protected $listeners = [
|
protected $listeners = [
|
||||||
'refresh' => 'refresh',
|
'refreshEnvs' => 'refresh',
|
||||||
|
'refresh',
|
||||||
'compose_loaded' => '$refresh',
|
'compose_loaded' => '$refresh',
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -129,7 +130,8 @@ class Show extends Component
|
|||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$this->env->delete();
|
$this->env->delete();
|
||||||
$this->dispatch('refreshEnvs');
|
$this->dispatch('environmentVariableDeleted');
|
||||||
|
$this->dispatch('success', 'Environment variable deleted successfully.');
|
||||||
} catch (\Exception $e) {
|
} catch (\Exception $e) {
|
||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
|
@@ -97,7 +97,7 @@ class GetLogs extends Component
|
|||||||
if (! $refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) {
|
if (! $refresh && ($this->resource?->getMorphClass() === 'App\Models\Service' || str($this->container)->contains('-pr-'))) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (! $this->numberOfLines) {
|
if ($this->numberOfLines <= 0) {
|
||||||
$this->numberOfLines = 1000;
|
$this->numberOfLines = 1000;
|
||||||
}
|
}
|
||||||
if ($this->container) {
|
if ($this->container) {
|
||||||
|
@@ -26,7 +26,7 @@ class All extends Component
|
|||||||
$this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name'));
|
$this->containerNames = $this->containerNames->merge($this->resource->databases()->pluck('name'));
|
||||||
} elseif ($this->resource->type() == 'application') {
|
} elseif ($this->resource->type() == 'application') {
|
||||||
if ($this->resource->build_pack === 'dockercompose') {
|
if ($this->resource->build_pack === 'dockercompose') {
|
||||||
$parsed = $this->resource->parseCompose();
|
$parsed = $this->resource->oldParser();
|
||||||
$containers = collect(data_get($parsed, 'services'))->keys();
|
$containers = collect(data_get($parsed, 'services'))->keys();
|
||||||
$this->containerNames = $containers;
|
$this->containerNames = $containers;
|
||||||
} else {
|
} else {
|
||||||
|
@@ -16,7 +16,7 @@ class Show extends Component
|
|||||||
|
|
||||||
public array $parameters;
|
public array $parameters;
|
||||||
|
|
||||||
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey' => 'saveKey'];
|
protected $listeners = ['refreshEnvs' => '$refresh', 'saveKey'];
|
||||||
|
|
||||||
public function saveKey($data)
|
public function saveKey($data)
|
||||||
{
|
{
|
||||||
|
@@ -3,6 +3,8 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use App\Enums\ApplicationDeploymentStatus;
|
use App\Enums\ApplicationDeploymentStatus;
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
|
use App\Jobs\ServerFilesFromServerJob;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
@@ -412,23 +414,6 @@ class Application extends BaseModel
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function dockerComposePrLocation(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
set: function ($value) {
|
|
||||||
if (is_null($value) || $value === '') {
|
|
||||||
return '/docker-compose.yaml';
|
|
||||||
} else {
|
|
||||||
if ($value !== '/') {
|
|
||||||
return Str::start(Str::replaceEnd('/', '', $value), '/');
|
|
||||||
}
|
|
||||||
|
|
||||||
return Str::start($value, '/');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function baseDirectory(): Attribute
|
public function baseDirectory(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@@ -479,12 +464,12 @@ class Application extends BaseModel
|
|||||||
$main_server_status = $this->destination->server->isFunctional();
|
$main_server_status = $this->destination->server->isFunctional();
|
||||||
foreach ($additional_servers_status as $status) {
|
foreach ($additional_servers_status as $status) {
|
||||||
$server_status = str($status)->before(':')->value();
|
$server_status = str($status)->before(':')->value();
|
||||||
if ($main_server_status !== $server_status) {
|
if ($server_status !== 'running') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return $main_server_status;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@@ -1040,7 +1025,7 @@ class Application extends BaseModel
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function parseRawCompose()
|
public function oldRawParser()
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$yaml = Yaml::parse($this->docker_compose_raw);
|
$yaml = Yaml::parse($this->docker_compose_raw);
|
||||||
@@ -1100,8 +1085,550 @@ class Application extends BaseModel
|
|||||||
instant_remote_process($commands, $this->destination->server, false);
|
instant_remote_process($commands, $this->destination->server, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function parseCompose(int $pull_request_id = 0, ?int $preview_id = null)
|
public function newParser(int $pull_request_id = 0, ?int $preview_id = null)
|
||||||
{
|
{
|
||||||
|
$pullRequestId = $pull_request_id;
|
||||||
|
$isPullRequest = $pullRequestId == 0 ? false : true;
|
||||||
|
|
||||||
|
$uuid = data_get($this, 'uuid');
|
||||||
|
$server = data_get($this, 'destination.server');
|
||||||
|
$compose = data_get($this, 'docker_compose_raw');
|
||||||
|
try {
|
||||||
|
$yaml = Yaml::parse($compose);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$services = data_get($yaml, 'services', collect([]));
|
||||||
|
$topLevel = collect([
|
||||||
|
'volumes' => collect(data_get($yaml, 'volumes', [])),
|
||||||
|
'networks' => collect(data_get($yaml, 'networks', [])),
|
||||||
|
'configs' => collect(data_get($yaml, 'configs', [])),
|
||||||
|
'secrets' => collect(data_get($yaml, 'secrets', [])),
|
||||||
|
]);
|
||||||
|
|
||||||
|
// If there are predefined volumes, make sure they are not null
|
||||||
|
if ($topLevel->get('volumes')->count() > 0) {
|
||||||
|
$temp = collect([]);
|
||||||
|
foreach ($topLevel['volumes'] as $volumeName => $volume) {
|
||||||
|
if (is_null($volume)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$temp->put($volumeName, $volume);
|
||||||
|
}
|
||||||
|
$topLevel['volumes'] = $temp;
|
||||||
|
}
|
||||||
|
// Get the base docker network
|
||||||
|
$baseNetwork = collect([$uuid]);
|
||||||
|
if ($isPullRequest) {
|
||||||
|
$baseNetwork = collect(["{$uuid}-{$pullRequestId}"]);
|
||||||
|
}
|
||||||
|
$parsedServices = collect([]);
|
||||||
|
$fileStorages = $this->fileStorages();
|
||||||
|
// Let's loop through the services
|
||||||
|
foreach ($services as $serviceName => $service) {
|
||||||
|
$image = data_get_str($service, 'image');
|
||||||
|
$restart = data_get_str($service, 'restart', RESTART_MODE);
|
||||||
|
$logging = data_get($service, 'logging');
|
||||||
|
|
||||||
|
if ($server->isLogDrainEnabled() && $this->isLogDrainEnabled()) {
|
||||||
|
$logging = [
|
||||||
|
'driver' => 'fluentd',
|
||||||
|
'options' => [
|
||||||
|
'fluentd-address' => 'tcp://127.0.0.1:24224',
|
||||||
|
'fluentd-async' => 'true',
|
||||||
|
'fluentd-sub-second-precision' => 'true',
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
$volumes = collect(data_get($service, 'volumes', []));
|
||||||
|
$networks = collect(data_get($service, 'networks', []));
|
||||||
|
$depends_on = collect(data_get($service, 'depends_on', []));
|
||||||
|
$labels = collect(data_get($service, 'labels', []));
|
||||||
|
$environment = collect(data_get($service, 'environment', []));
|
||||||
|
$buildArgs = collect(data_get($service, 'build.args', []));
|
||||||
|
$environment = $environment->merge($buildArgs);
|
||||||
|
|
||||||
|
$baseName = generateApplicationContainerName(
|
||||||
|
application: $this,
|
||||||
|
pull_request_id: $pullRequestId
|
||||||
|
);
|
||||||
|
$containerName = "$serviceName-$baseName";
|
||||||
|
$volumesParsed = collect([]);
|
||||||
|
|
||||||
|
if ($volumes->count() > 0) {
|
||||||
|
foreach ($volumes as $index => $volume) {
|
||||||
|
$type = null;
|
||||||
|
$source = null;
|
||||||
|
$target = null;
|
||||||
|
$content = null;
|
||||||
|
$isDirectory = false;
|
||||||
|
if (is_string($volume)) {
|
||||||
|
$source = str($volume)->before(':');
|
||||||
|
$target = str($volume)->after(':')->beforeLast(':');
|
||||||
|
$foundConfig = $fileStorages->whereMountPath($target)->first();
|
||||||
|
if (sourceIsLocal($source)) {
|
||||||
|
$type = str('bind');
|
||||||
|
if ($foundConfig) {
|
||||||
|
$contentNotNull_temp = data_get($foundConfig, 'content');
|
||||||
|
if ($contentNotNull_temp) {
|
||||||
|
$content = $contentNotNull_temp;
|
||||||
|
}
|
||||||
|
$isDirectory = data_get($foundConfig, 'is_directory');
|
||||||
|
} else {
|
||||||
|
// By default, we cannot determine if the bind is a directory or not, so we set it to directory
|
||||||
|
$isDirectory = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$type = str('volume');
|
||||||
|
}
|
||||||
|
} elseif (is_array($volume)) {
|
||||||
|
$type = data_get_str($volume, 'type');
|
||||||
|
$source = data_get_str($volume, 'source');
|
||||||
|
$target = data_get_str($volume, 'target');
|
||||||
|
$content = data_get($volume, 'content');
|
||||||
|
$isDirectory = (bool) data_get($volume, 'isDirectory', null) || (bool) data_get($volume, 'is_directory', null);
|
||||||
|
|
||||||
|
$foundConfig = $fileStorages->whereMountPath($target)->first();
|
||||||
|
if ($foundConfig) {
|
||||||
|
$contentNotNull_temp = data_get($foundConfig, 'content');
|
||||||
|
if ($contentNotNull_temp) {
|
||||||
|
$content = $contentNotNull_temp;
|
||||||
|
}
|
||||||
|
$isDirectory = data_get($foundConfig, 'is_directory');
|
||||||
|
} else {
|
||||||
|
// if isDirectory is not set (or false) & content is also not set, we assume it is a directory
|
||||||
|
if ((is_null($isDirectory) || ! $isDirectory) && is_null($content)) {
|
||||||
|
$isDirectory = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($type->value() === 'bind') {
|
||||||
|
if ($source->value() === '/var/run/docker.sock') {
|
||||||
|
return $volume;
|
||||||
|
}
|
||||||
|
if ($source->value() === '/tmp' || $source->value() === '/tmp/') {
|
||||||
|
return $volume;
|
||||||
|
}
|
||||||
|
$mainDirectory = str(base_configuration_dir().'/applications/'.$uuid);
|
||||||
|
$source = replaceLocalSource($source, $mainDirectory);
|
||||||
|
if ($isPullRequest) {
|
||||||
|
$source = $source."-pr-$pullRequestId";
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
! $this?->settings?->is_preserve_repository_enabled || $foundConfig?->is_based_on_git
|
||||||
|
) {
|
||||||
|
LocalFileVolume::updateOrCreate(
|
||||||
|
[
|
||||||
|
'mount_path' => $target,
|
||||||
|
'resource_id' => $this->id,
|
||||||
|
'resource_type' => get_class($this),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'fs_path' => $source,
|
||||||
|
'mount_path' => $target,
|
||||||
|
'content' => $content,
|
||||||
|
'is_directory' => $isDirectory,
|
||||||
|
'resource_id' => $this->id,
|
||||||
|
'resource_type' => get_class($this),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$volume = "$source:$target";
|
||||||
|
} elseif ($type->value() === 'volume') {
|
||||||
|
if ($topLevel->get('volumes')->has($source->value())) {
|
||||||
|
$temp = $topLevel->get('volumes')->get($source->value());
|
||||||
|
if (data_get($temp, 'driver_opts.type') === 'cifs') {
|
||||||
|
return $volume;
|
||||||
|
}
|
||||||
|
if (data_get($temp, 'driver_opts.type') === 'nfs') {
|
||||||
|
return $volume;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$slugWithoutUuid = Str::slug($source, '-');
|
||||||
|
$name = "{$uuid}_{$slugWithoutUuid}";
|
||||||
|
if ($isPullRequest) {
|
||||||
|
$name = "{$name}-pr-$pullRequestId";
|
||||||
|
}
|
||||||
|
if (is_string($volume)) {
|
||||||
|
$source = str($volume)->before(':');
|
||||||
|
$target = str($volume)->after(':')->beforeLast(':');
|
||||||
|
$source = $name;
|
||||||
|
$volume = "$source:$target";
|
||||||
|
} elseif (is_array($volume)) {
|
||||||
|
data_set($volume, 'source', $name);
|
||||||
|
}
|
||||||
|
$topLevel->get('volumes')->put($name, [
|
||||||
|
'name' => $name,
|
||||||
|
]);
|
||||||
|
|
||||||
|
LocalPersistentVolume::updateOrCreate(
|
||||||
|
[
|
||||||
|
'mount_path' => $target,
|
||||||
|
'resource_id' => $this->id,
|
||||||
|
'resource_type' => get_class($this),
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'name' => $name,
|
||||||
|
'mount_path' => $target,
|
||||||
|
'resource_id' => $this->id,
|
||||||
|
'resource_type' => get_class($this),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
dispatch(new ServerFilesFromServerJob($this));
|
||||||
|
$volumesParsed->put($index, $volume);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($depends_on?->count() > 0) {
|
||||||
|
if ($isPullRequest) {
|
||||||
|
$newDependsOn = collect([]);
|
||||||
|
$depends_on->each(function ($dependency, $condition) use ($pullRequestId, $newDependsOn) {
|
||||||
|
if (is_numeric($condition)) {
|
||||||
|
$dependency = "$dependency-pr-$pullRequestId";
|
||||||
|
|
||||||
|
$newDependsOn->put($condition, $dependency);
|
||||||
|
} else {
|
||||||
|
$condition = "$condition-pr-$pullRequestId";
|
||||||
|
$newDependsOn->put($condition, $dependency);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
$depends_on = $newDependsOn;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
if ($topLevel->get('networks')?->count() > 0) {
|
||||||
|
foreach ($topLevel->get('networks') as $networkName => $network) {
|
||||||
|
if ($networkName === 'default') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// ignore aliases
|
||||||
|
if ($network['aliases'] ?? false) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$networkExists = $networks->contains(function ($value, $key) use ($networkName) {
|
||||||
|
return $value == $networkName || $key == $networkName;
|
||||||
|
});
|
||||||
|
if (! $networkExists) {
|
||||||
|
$networks->put($networkName, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$baseNetworkExists = $networks->contains(function ($value, $_) use ($baseNetwork) {
|
||||||
|
return $value == $baseNetwork;
|
||||||
|
});
|
||||||
|
if (! $baseNetworkExists) {
|
||||||
|
foreach ($baseNetwork as $network) {
|
||||||
|
$topLevel->get('networks')->put($network, [
|
||||||
|
'name' => $network,
|
||||||
|
'external' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$networks_temp = collect();
|
||||||
|
|
||||||
|
foreach ($networks as $key => $network) {
|
||||||
|
if (gettype($network) === 'string') {
|
||||||
|
// networks:
|
||||||
|
// - appwrite
|
||||||
|
$networks_temp->put($network, null);
|
||||||
|
} elseif (gettype($network) === 'array') {
|
||||||
|
// networks:
|
||||||
|
// default:
|
||||||
|
// ipv4_address: 192.168.203.254
|
||||||
|
$networks_temp->put($key, $network);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($baseNetwork as $key => $network) {
|
||||||
|
$networks_temp->put($network, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data_get($this, 'settings.connect_to_docker_network')) {
|
||||||
|
$network = $this->destination->network;
|
||||||
|
$networks_temp->put($network, null);
|
||||||
|
$topLevel->get('networks')->put($network, [
|
||||||
|
'name' => $network,
|
||||||
|
'external' => true,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($environment as $key => $value) {
|
||||||
|
if (is_numeric($key)) {
|
||||||
|
if (is_array($value)) {
|
||||||
|
// - SESSION_SECRET: 123
|
||||||
|
// - SESSION_SECRET:
|
||||||
|
$key = str(collect($value)->keys()->first());
|
||||||
|
$value = str(collect($value)->values()->first());
|
||||||
|
} else {
|
||||||
|
$variable = str($value);
|
||||||
|
if ($variable->contains('=')) {
|
||||||
|
// - SESSION_SECRET=123
|
||||||
|
// - SESSION_SECRET=
|
||||||
|
$key = $variable->before('=');
|
||||||
|
$value = $variable->after('=');
|
||||||
|
} else {
|
||||||
|
// - SESSION_SECRET
|
||||||
|
$key = $variable;
|
||||||
|
$value = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// SESSION_SECRET: 123
|
||||||
|
// SESSION_SECRET:
|
||||||
|
$key = str($key);
|
||||||
|
$value = str($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto generate FQDN and URL
|
||||||
|
if ($key->startsWith('SERVICE_FQDN') || $value->startsWith('$SERVICE_FQDN') || $value->startsWith('${SERVICE_FQDN')) {
|
||||||
|
if ($value->contains('SERVICE_FQDN')) {
|
||||||
|
$key = str(replaceVariables($value));
|
||||||
|
$value = null;
|
||||||
|
}
|
||||||
|
$name = $key->after('SERVICE_FQDN_')->beforeLast('_')->lower();
|
||||||
|
$fqdn = generateFqdn($server, "{$name->value()}-{$uuid}");
|
||||||
|
|
||||||
|
$url = str($fqdn)->replace('http://', '')->replace('https://', '')->replace('www.', '');
|
||||||
|
$keyUrl = $key->replace('SERVICE_FQDN', 'SERVICE_URL');
|
||||||
|
// TODO: is this needed?
|
||||||
|
// if (substr_count($key->value(), '_') === 3) {
|
||||||
|
// // SERVICE_FQDN_UMAMI_1000
|
||||||
|
// $port = $key->afterLast('_');
|
||||||
|
// } else {
|
||||||
|
// // SERVICE_FQDN_UMAMI
|
||||||
|
// $port = null;
|
||||||
|
// }
|
||||||
|
// if ($port) {
|
||||||
|
// $fqdn = "$fqdn:$port";
|
||||||
|
// }
|
||||||
|
if ($value && get_class($value) === 'Illuminate\Support\Stringable') {
|
||||||
|
$path = $value->value();
|
||||||
|
$fqdn = "$fqdn$path";
|
||||||
|
}
|
||||||
|
|
||||||
|
// ray([
|
||||||
|
// 'key' => $key,
|
||||||
|
// 'value' => $fqdn,
|
||||||
|
// ]);
|
||||||
|
// ray($this->environment_variables()->where('key', $key)->where('application_id', $this->id)->first());
|
||||||
|
$this->environment_variables()->where('key', $key)->where('application_id', $this->id)->firstOrCreate([
|
||||||
|
'key' => $key,
|
||||||
|
'application_id' => $this->id,
|
||||||
|
], [
|
||||||
|
'value' => $fqdn,
|
||||||
|
'is_build_time' => false,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
$this->environment_variables()->where('key', $keyUrl)->where('application_id', $this->id)->firstOrCreate([
|
||||||
|
'key' => $keyUrl,
|
||||||
|
'application_id' => $this->id,
|
||||||
|
], [
|
||||||
|
'value' => $url,
|
||||||
|
'is_build_time' => false,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
} elseif ($value->startsWith('$')) {
|
||||||
|
// If the value is a variable then we will add it to Coolify's DB
|
||||||
|
|
||||||
|
// ${VARIABLE} will be VARIABLE instead
|
||||||
|
$value = str(replaceVariables($value));
|
||||||
|
if ($value->contains(':-')) {
|
||||||
|
$key = $value->before(':');
|
||||||
|
$defaultValue = $value->after(':-');
|
||||||
|
} elseif ($value->contains('-')) {
|
||||||
|
$key = $value->before('-');
|
||||||
|
$defaultValue = $value->after('-');
|
||||||
|
} elseif ($value->contains(':?')) {
|
||||||
|
$key = $value->before(':');
|
||||||
|
$defaultValue = $value->after(':?');
|
||||||
|
} elseif ($value->contains('?')) {
|
||||||
|
$key = $value->before('?');
|
||||||
|
$defaultValue = $value->after('?');
|
||||||
|
} else {
|
||||||
|
$key = $value;
|
||||||
|
$defaultValue = null;
|
||||||
|
}
|
||||||
|
$this->environment_variables()->where('key', $key)->where('application_id', $this->id)->firstOrCreate([
|
||||||
|
'key' => $key,
|
||||||
|
'application_id' => $this->id,
|
||||||
|
'is_preview' => false,
|
||||||
|
], [
|
||||||
|
'value' => $defaultValue,
|
||||||
|
'is_build_time' => false,
|
||||||
|
'is_preview' => false,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$branch = $this->git_branch;
|
||||||
|
if ($pullRequestId !== 0) {
|
||||||
|
$branch = "pull/{$pullRequestId}/head";
|
||||||
|
}
|
||||||
|
if ($this->environment_variables->where('key', 'COOLIFY_BRANCH')->isEmpty()) {
|
||||||
|
$environment->put('COOLIFY_BRANCH', $branch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($this->environment_variables->where('key', 'COOLIFY_CONTAINER_NAME')->isEmpty()) {
|
||||||
|
$environment->put('COOLIFY_CONTAINER_NAME', $containerName);
|
||||||
|
}
|
||||||
|
// Remove SERVICE_FQDN and SERVICE_URL from environment
|
||||||
|
$environment = $environment->filter(function ($value, $key) {
|
||||||
|
return ! str($key)->startsWith('SERVICE_FQDN') && ! str($key)->startsWith('SERVICE_URL');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Labels
|
||||||
|
$fqdns = collect([]);
|
||||||
|
|
||||||
|
$domains = collect(json_decode($this->docker_compose_domains)) ?? collect([]);
|
||||||
|
if ($domains->count() !== 0) {
|
||||||
|
$fqdns = data_get($domains, "$serviceName.domain");
|
||||||
|
if (! $fqdns) {
|
||||||
|
$fqdns = collect([]);
|
||||||
|
} else {
|
||||||
|
$fqdns = str($fqdns)->explode(',');
|
||||||
|
if ($isPullRequest) {
|
||||||
|
$preview = $this->previews()->find($preview_id);
|
||||||
|
$docker_compose_domains = collect(json_decode(data_get($preview, 'docker_compose_domains')));
|
||||||
|
if ($docker_compose_domains->count() > 0) {
|
||||||
|
$found_fqdn = data_get($docker_compose_domains, "$serviceName.domain");
|
||||||
|
if ($found_fqdn) {
|
||||||
|
$fqdns = collect($found_fqdn);
|
||||||
|
} else {
|
||||||
|
$fqdns = collect([]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$fqdns = $fqdns->map(function ($fqdn) use ($pullRequestId) {
|
||||||
|
$preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->id, $pullRequestId);
|
||||||
|
$url = Url::fromString($fqdn);
|
||||||
|
$template = $this->preview_url_template;
|
||||||
|
$host = $url->getHost();
|
||||||
|
$schema = $url->getScheme();
|
||||||
|
$random = new Cuid2;
|
||||||
|
$preview_fqdn = str_replace('{{random}}', $random, $template);
|
||||||
|
$preview_fqdn = str_replace('{{domain}}', $host, $preview_fqdn);
|
||||||
|
$preview_fqdn = str_replace('{{pr_id}}', $pullRequestId, $preview_fqdn);
|
||||||
|
$preview_fqdn = "$schema://$preview_fqdn";
|
||||||
|
$preview->fqdn = $preview_fqdn;
|
||||||
|
$preview->save();
|
||||||
|
|
||||||
|
return $preview_fqdn;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$shouldGenerateLabelsExactly = $server->settings->generate_exact_labels;
|
||||||
|
if ($shouldGenerateLabelsExactly) {
|
||||||
|
switch ($server->proxyType()) {
|
||||||
|
case ProxyTypes::TRAEFIK->value:
|
||||||
|
$labels = $labels->merge(
|
||||||
|
fqdnLabelsForTraefik(
|
||||||
|
uuid: $this->uuid,
|
||||||
|
domains: $fqdns,
|
||||||
|
serviceLabels: $labels,
|
||||||
|
generate_unique_uuid: $this->build_pack === 'dockercompose',
|
||||||
|
image: $image,
|
||||||
|
is_force_https_enabled: $this->isForceHttpsEnabled(),
|
||||||
|
is_gzip_enabled: $this->isGzipEnabled(),
|
||||||
|
is_stripprefix_enabled: $this->isStripprefixEnabled(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case ProxyTypes::CADDY->value:
|
||||||
|
$labels = $labels->merge(
|
||||||
|
fqdnLabelsForCaddy(
|
||||||
|
network: $this->destination->network,
|
||||||
|
uuid: $this->uuid,
|
||||||
|
domains: $fqdns,
|
||||||
|
serviceLabels: $labels,
|
||||||
|
image: $image,
|
||||||
|
is_force_https_enabled: $this->isForceHttpsEnabled(),
|
||||||
|
is_gzip_enabled: $this->isGzipEnabled(),
|
||||||
|
is_stripprefix_enabled: $this->isStripprefixEnabled(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$labels = $labels->merge(
|
||||||
|
fqdnLabelsForTraefik(
|
||||||
|
uuid: $this->uuid,
|
||||||
|
domains: $fqdns,
|
||||||
|
serviceLabels: $labels,
|
||||||
|
generate_unique_uuid: $this->build_pack === 'dockercompose',
|
||||||
|
image: $image,
|
||||||
|
is_force_https_enabled: $this->isForceHttpsEnabled(),
|
||||||
|
is_gzip_enabled: $this->isGzipEnabled(),
|
||||||
|
is_stripprefix_enabled: $this->isStripprefixEnabled(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
$labels = $labels->merge(
|
||||||
|
fqdnLabelsForCaddy(
|
||||||
|
network: $this->destination->network,
|
||||||
|
uuid: $this->uuid,
|
||||||
|
domains: $fqdns,
|
||||||
|
serviceLabels: $labels,
|
||||||
|
image: $image,
|
||||||
|
is_force_https_enabled: $this->isForceHttpsEnabled(),
|
||||||
|
is_gzip_enabled: $this->isGzipEnabled(),
|
||||||
|
is_stripprefix_enabled: $this->isStripprefixEnabled(),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$defaultLabels = defaultLabels(
|
||||||
|
id: $this->id,
|
||||||
|
name: $containerName,
|
||||||
|
pull_request_id: $pullRequestId,
|
||||||
|
type: 'application');
|
||||||
|
$labels = $labels->merge($defaultLabels);
|
||||||
|
|
||||||
|
if ($labels->count() > 0 && $this->settings->is_container_label_escape_enabled) {
|
||||||
|
$labels = $labels->map(function ($value, $key) {
|
||||||
|
return escapeDollarSign($value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
$payload = collect($service)->merge([
|
||||||
|
'restart' => $restart->value(),
|
||||||
|
'container_name' => $containerName,
|
||||||
|
'volumes' => $volumesParsed,
|
||||||
|
'networks' => $networks_temp,
|
||||||
|
'labels' => $labels,
|
||||||
|
'environment' => $environment,
|
||||||
|
]);
|
||||||
|
|
||||||
|
if ($logging) {
|
||||||
|
$payload['logging'] = $logging;
|
||||||
|
}
|
||||||
|
if ($depends_on->count() > 0) {
|
||||||
|
$payload['depends_on'] = $depends_on;
|
||||||
|
}
|
||||||
|
if ($isPullRequest) {
|
||||||
|
$serviceName = "{$serviceName}-pr-{$pullRequestId}";
|
||||||
|
}
|
||||||
|
$parsedServices->put($serviceName, $payload);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
$topLevel->put('services', $parsedServices);
|
||||||
|
$customOrder = ['services', 'volumes', 'networks', 'configs', 'secrets'];
|
||||||
|
|
||||||
|
$topLevel = $topLevel->sortBy(function ($value, $key) use ($customOrder) {
|
||||||
|
return array_search($key, $customOrder);
|
||||||
|
});
|
||||||
|
$this->docker_compose = Yaml::dump(convertToArray($topLevel), 10, 2);
|
||||||
|
data_forget($this, 'environment_variables');
|
||||||
|
data_forget($this, 'environment_variables_preview');
|
||||||
|
$this->save();
|
||||||
|
|
||||||
|
return $topLevel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function oldParser(int $pull_request_id = 0, ?int $preview_id = null)
|
||||||
|
{
|
||||||
|
if ($this->compose_parsing_version === '3') {
|
||||||
|
return $this->newParser($pull_request_id, $preview_id);
|
||||||
|
} else
|
||||||
if ($this->docker_compose_raw) {
|
if ($this->docker_compose_raw) {
|
||||||
return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);
|
return parseDockerComposeFile(resource: $this, isNew: false, pull_request_id: $pull_request_id, preview_id: $preview_id);
|
||||||
} else {
|
} else {
|
||||||
@@ -1154,7 +1681,7 @@ class Application extends BaseModel
|
|||||||
if ($composeFileContent) {
|
if ($composeFileContent) {
|
||||||
$this->docker_compose_raw = $composeFileContent;
|
$this->docker_compose_raw = $composeFileContent;
|
||||||
$this->save();
|
$this->save();
|
||||||
$parsedServices = $this->parseCompose();
|
$parsedServices = $this->oldParser();
|
||||||
if ($this->docker_compose_domains) {
|
if ($this->docker_compose_domains) {
|
||||||
$json = collect(json_decode($this->docker_compose_domains));
|
$json = collect(json_decode($this->docker_compose_domains));
|
||||||
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray();
|
$names = collect(data_get($parsedServices, 'services'))->keys()->toArray();
|
||||||
|
@@ -14,7 +14,7 @@ class ApplicationPreview extends BaseModel
|
|||||||
static::deleting(function ($preview) {
|
static::deleting(function ($preview) {
|
||||||
if ($preview->application->build_pack === 'dockercompose') {
|
if ($preview->application->build_pack === 'dockercompose') {
|
||||||
$server = $preview->application->destination->server;
|
$server = $preview->application->destination->server;
|
||||||
$composeFile = $preview->application->parseCompose(pull_request_id: $preview->pull_request_id);
|
$composeFile = $preview->application->oldParser(pull_request_id: $preview->pull_request_id);
|
||||||
$volumes = data_get($composeFile, 'volumes');
|
$volumes = data_get($composeFile, 'volumes');
|
||||||
$networks = data_get($composeFile, 'networks');
|
$networks = data_get($composeFile, 'networks');
|
||||||
$networkKeys = collect($networks)->keys();
|
$networkKeys = collect($networks)->keys();
|
||||||
|
@@ -6,7 +6,6 @@ use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
|||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Model;
|
use Illuminate\Database\Eloquent\Model;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
|
||||||
use Visus\Cuid2\Cuid2;
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
#[OA\Schema(
|
#[OA\Schema(
|
||||||
@@ -97,8 +96,22 @@ class EnvironmentVariable extends Model
|
|||||||
$resource = Application::find($this->application_id);
|
$resource = Application::find($this->application_id);
|
||||||
} elseif ($this->service_id) {
|
} elseif ($this->service_id) {
|
||||||
$resource = Service::find($this->service_id);
|
$resource = Service::find($this->service_id);
|
||||||
} elseif ($this->database_id) {
|
} elseif ($this->standalone_postgresql_id) {
|
||||||
$resource = getResourceByUuid($this->parameters['database_uuid'], data_get(auth()->user()->currentTeam(), 'id'));
|
$resource = StandalonePostgresql::find($this->standalone_postgresql_id);
|
||||||
|
} elseif ($this->standalone_redis_id) {
|
||||||
|
$resource = StandaloneRedis::find($this->standalone_redis_id);
|
||||||
|
} elseif ($this->standalone_mongodb_id) {
|
||||||
|
$resource = StandaloneMongodb::find($this->standalone_mongodb_id);
|
||||||
|
} elseif ($this->standalone_mysql_id) {
|
||||||
|
$resource = StandaloneMysql::find($this->standalone_mysql_id);
|
||||||
|
} elseif ($this->standalone_mariadb_id) {
|
||||||
|
$resource = StandaloneMariadb::find($this->standalone_mariadb_id);
|
||||||
|
} elseif ($this->standalone_keydb_id) {
|
||||||
|
$resource = StandaloneKeydb::find($this->standalone_keydb_id);
|
||||||
|
} elseif ($this->standalone_dragonfly_id) {
|
||||||
|
$resource = StandaloneDragonfly::find($this->standalone_dragonfly_id);
|
||||||
|
} elseif ($this->standalone_clickhouse_id) {
|
||||||
|
$resource = StandaloneClickhouse::find($this->standalone_clickhouse_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $resource;
|
return $resource;
|
||||||
@@ -122,63 +135,6 @@ class EnvironmentVariable extends Model
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function isFoundInCompose(): Attribute
|
|
||||||
{
|
|
||||||
return Attribute::make(
|
|
||||||
get: function () {
|
|
||||||
if (! $this->application_id) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
$found_in_compose = false;
|
|
||||||
$found_in_args = false;
|
|
||||||
$resource = $this->resource();
|
|
||||||
$compose = data_get($resource, 'docker_compose_raw');
|
|
||||||
if (! $compose) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
$yaml = Yaml::parse($compose);
|
|
||||||
$services = collect(data_get($yaml, 'services'));
|
|
||||||
if ($services->isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
foreach ($services as $service) {
|
|
||||||
$environments = collect(data_get($service, 'environment'));
|
|
||||||
$args = collect(data_get($service, 'build.args'));
|
|
||||||
if ($environments->isEmpty() && $args->isEmpty()) {
|
|
||||||
$found_in_compose = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$found_in_compose = $environments->contains(function ($item) {
|
|
||||||
if (str($item)->contains('=')) {
|
|
||||||
$item = str($item)->before('=');
|
|
||||||
}
|
|
||||||
|
|
||||||
return strpos($item, $this->key) !== false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($found_in_compose) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
$found_in_args = $args->contains(function ($item) {
|
|
||||||
if (str($item)->contains('=')) {
|
|
||||||
$item = str($item)->before('=');
|
|
||||||
}
|
|
||||||
|
|
||||||
return strpos($item, $this->key) !== false;
|
|
||||||
});
|
|
||||||
|
|
||||||
if ($found_in_args) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $found_in_compose || $found_in_args;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function isShared(): Attribute
|
protected function isShared(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
@@ -201,8 +157,10 @@ class EnvironmentVariable extends Model
|
|||||||
$environment_variable = trim($environment_variable);
|
$environment_variable = trim($environment_variable);
|
||||||
$sharedEnvsFound = str($environment_variable)->matchAll('/{{(.*?)}}/');
|
$sharedEnvsFound = str($environment_variable)->matchAll('/{{(.*?)}}/');
|
||||||
if ($sharedEnvsFound->isEmpty()) {
|
if ($sharedEnvsFound->isEmpty()) {
|
||||||
|
|
||||||
return $environment_variable;
|
return $environment_variable;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach ($sharedEnvsFound as $sharedEnv) {
|
foreach ($sharedEnvsFound as $sharedEnv) {
|
||||||
$type = str($sharedEnv)->match('/(.*?)\./');
|
$type = str($sharedEnv)->match('/(.*?)\./');
|
||||||
if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
if (! collect(SHARED_VARIABLE_TYPES)->contains($type)) {
|
||||||
|
@@ -24,8 +24,9 @@ class LocalFileVolume extends BaseModel
|
|||||||
return $this->morphTo('resource');
|
return $this->morphTo('resource');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function deleteStorageOnServer()
|
public function loadStorageOnServer()
|
||||||
{
|
{
|
||||||
|
$this->load(['service']);
|
||||||
$isService = data_get($this->resource, 'service');
|
$isService = data_get($this->resource, 'service');
|
||||||
if ($isService) {
|
if ($isService) {
|
||||||
$workdir = $this->resource->service->workdir();
|
$workdir = $this->resource->service->workdir();
|
||||||
@@ -35,17 +36,46 @@ class LocalFileVolume extends BaseModel
|
|||||||
$server = $this->resource->destination->server;
|
$server = $this->resource->destination->server;
|
||||||
}
|
}
|
||||||
$commands = collect([]);
|
$commands = collect([]);
|
||||||
$fs_path = data_get($this, 'fs_path');
|
$path = data_get_str($this, 'fs_path');
|
||||||
$isFile = instant_remote_process(["test -f $fs_path && echo OK || echo NOK"], $server);
|
if ($path->startsWith('.')) {
|
||||||
$isDir = instant_remote_process(["test -d $fs_path && echo OK || echo NOK"], $server);
|
$path = $path->after('.');
|
||||||
if ($fs_path && $fs_path != '/' && $fs_path != '.' && $fs_path != '..') {
|
$path = $workdir.$path;
|
||||||
ray($isFile, $isDir);
|
}
|
||||||
|
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
||||||
|
if ($isFile === 'OK') {
|
||||||
|
$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') {
|
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') {
|
} elseif ($isDir === 'OK') {
|
||||||
$commands->push("rm -rf $fs_path > /dev/null 2>&1 || true");
|
$commands->push("rm -rf $path > /dev/null 2>&1 || true");
|
||||||
$commands->push("rmdir $fs_path > /dev/null 2>&1 || true");
|
$commands->push("rmdir $path > /dev/null 2>&1 || true");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($commands->count() > 0) {
|
if ($commands->count() > 0) {
|
||||||
@@ -55,6 +85,7 @@ class LocalFileVolume extends BaseModel
|
|||||||
|
|
||||||
public function saveStorageOnServer()
|
public function saveStorageOnServer()
|
||||||
{
|
{
|
||||||
|
$this->load(['service']);
|
||||||
$isService = data_get($this->resource, 'service');
|
$isService = data_get($this->resource, 'service');
|
||||||
if ($isService) {
|
if ($isService) {
|
||||||
$workdir = $this->resource->service->workdir();
|
$workdir = $this->resource->service->workdir();
|
||||||
@@ -74,30 +105,36 @@ class LocalFileVolume extends BaseModel
|
|||||||
$commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true");
|
$commands->push("mkdir -p $parent_dir > /dev/null 2>&1 || true");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$fileVolume = $this;
|
$path = data_get_str($this, 'fs_path');
|
||||||
$path = str(data_get($fileVolume, 'fs_path'));
|
$content = data_get($this, 'content');
|
||||||
$content = data_get($fileVolume, 'content');
|
|
||||||
if ($path->startsWith('.')) {
|
if ($path->startsWith('.')) {
|
||||||
$path = $path->after('.');
|
$path = $path->after('.');
|
||||||
$path = $workdir.$path;
|
$path = $workdir.$path;
|
||||||
}
|
}
|
||||||
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
$isFile = instant_remote_process(["test -f $path && echo OK || echo NOK"], $server);
|
||||||
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
$isDir = instant_remote_process(["test -d $path && echo OK || echo NOK"], $server);
|
||||||
if ($isFile == 'OK' && $fileVolume->is_directory) {
|
if ($isFile == 'OK' && $this->is_directory) {
|
||||||
$content = instant_remote_process(["cat $path"], $server, false);
|
$content = instant_remote_process(["cat $path"], $server, false);
|
||||||
$fileVolume->is_directory = false;
|
$this->is_directory = false;
|
||||||
$fileVolume->content = $content;
|
$this->content = $content;
|
||||||
$fileVolume->save();
|
$this->save();
|
||||||
FileStorageChanged::dispatch(data_get($server, 'team_id'));
|
FileStorageChanged::dispatch(data_get($server, 'team_id'));
|
||||||
throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.');
|
throw new \Exception('The following file is a file on the server, but you are trying to mark it as a directory. Please delete the file on the server or mark it as directory.');
|
||||||
} elseif ($isDir == 'OK' && ! $fileVolume->is_directory) {
|
} elseif ($isDir == 'OK' && ! $this->is_directory) {
|
||||||
$fileVolume->is_directory = true;
|
if ($path == '/' || $path == '.' || $path == '..' || $path == '' || str($path)->isEmpty() || is_null($path)) {
|
||||||
$fileVolume->save();
|
$this->is_directory = true;
|
||||||
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.');
|
$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) {
|
if ($isDir == 'NOK' && ! $this->is_directory) {
|
||||||
$chmod = data_get($fileVolume, 'chmod');
|
$chmod = data_get($this, 'chmod');
|
||||||
$chown = data_get($fileVolume, 'chown');
|
$chown = data_get($this, 'chown');
|
||||||
if ($content) {
|
if ($content) {
|
||||||
$content = base64_encode($content);
|
$content = base64_encode($content);
|
||||||
$commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
|
$commands->push("echo '$content' | base64 -d | tee $path > /dev/null");
|
||||||
@@ -111,7 +148,7 @@ class LocalFileVolume extends BaseModel
|
|||||||
if ($chmod) {
|
if ($chmod) {
|
||||||
$commands->push("chmod $chmod $path");
|
$commands->push("chmod $chmod $path");
|
||||||
}
|
}
|
||||||
} elseif ($isDir == 'NOK' && $fileVolume->is_directory) {
|
} elseif ($isDir == 'NOK' && $this->is_directory) {
|
||||||
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -26,6 +26,6 @@ class ScheduledTask extends BaseModel
|
|||||||
|
|
||||||
public function executions(): HasMany
|
public function executions(): HasMany
|
||||||
{
|
{
|
||||||
return $this->hasMany(ScheduledTaskExecution::class);
|
return $this->hasMany(ScheduledTaskExecution::class)->orderBy('created_at', 'desc');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -649,7 +649,7 @@ $schema://$host {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getDiskUsage()
|
public function getDiskUsage(): ?string
|
||||||
{
|
{
|
||||||
return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false);
|
return instant_remote_process(["df /| tail -1 | awk '{ print $5}' | sed 's/%//g'"], $this, false);
|
||||||
}
|
}
|
||||||
|
@@ -2,11 +2,13 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use App\Enums\ProxyTypes;
|
||||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use OpenApi\Attributes as OA;
|
use OpenApi\Attributes as OA;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
@@ -23,6 +25,7 @@ use Symfony\Component\Yaml\Yaml;
|
|||||||
'description' => ['type' => 'string', 'description' => 'The description of the service.'],
|
'description' => ['type' => 'string', 'description' => 'The description of the service.'],
|
||||||
'docker_compose_raw' => ['type' => 'string', 'description' => 'The raw docker-compose.yml file of the service.'],
|
'docker_compose_raw' => ['type' => 'string', 'description' => 'The raw docker-compose.yml file of the service.'],
|
||||||
'docker_compose' => ['type' => 'string', 'description' => 'The docker-compose.yml file that is parsed and modified by Coolify.'],
|
'docker_compose' => ['type' => 'string', 'description' => 'The docker-compose.yml file that is parsed and modified by Coolify.'],
|
||||||
|
'destination_type' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
|
||||||
'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
|
'destination_id' => ['type' => 'integer', 'description' => 'The unique identifier of the destination where the service is running.'],
|
||||||
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
'connect_to_docker_network' => ['type' => 'boolean', 'description' => 'The flag to connect the service to the predefined Docker network.'],
|
||||||
'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'],
|
'is_container_label_escape_enabled' => ['type' => 'boolean', 'description' => 'The flag to enable the container label escape.'],
|
||||||
@@ -205,6 +208,41 @@ class Service extends BaseModel
|
|||||||
foreach ($applications as $application) {
|
foreach ($applications as $application) {
|
||||||
$image = str($application->image)->before(':')->value();
|
$image = str($application->image)->before(':')->value();
|
||||||
switch ($image) {
|
switch ($image) {
|
||||||
|
case str($image)?->contains('rabbitmq'):
|
||||||
|
$data = collect([]);
|
||||||
|
$host_port = $this->environment_variables()->where('key', 'PORT')->first();
|
||||||
|
$username = $this->environment_variables()->where('key', 'SERVICE_USER_RABBITMQ')->first();
|
||||||
|
$password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_RABBITMQ')->first();
|
||||||
|
if ($host_port) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'Host Port Binding' => [
|
||||||
|
'key' => data_get($host_port, 'key'),
|
||||||
|
'value' => data_get($host_port, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($username) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'Username' => [
|
||||||
|
'key' => data_get($username, 'key'),
|
||||||
|
'value' => data_get($username, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
if ($password) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'Password' => [
|
||||||
|
'key' => data_get($password, 'key'),
|
||||||
|
'value' => data_get($password, 'value'),
|
||||||
|
'rules' => 'required',
|
||||||
|
'isPassword' => true,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
$fields->put('RabbitMQ', $data->toArray());
|
||||||
|
break;
|
||||||
case str($image)?->contains('tolgee'):
|
case str($image)?->contains('tolgee'):
|
||||||
$data = collect([]);
|
$data = collect([]);
|
||||||
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first();
|
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_TOLGEE')->first();
|
||||||
@@ -504,6 +542,9 @@ class Service extends BaseModel
|
|||||||
default:
|
default:
|
||||||
$data = collect([]);
|
$data = collect([]);
|
||||||
$admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
|
$admin_user = $this->environment_variables()->where('key', 'SERVICE_USER_ADMIN')->first();
|
||||||
|
// Chaskiq
|
||||||
|
$admin_email = $this->environment_variables()->where('key', 'ADMIN_EMAIL')->first();
|
||||||
|
|
||||||
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
|
$admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_ADMIN')->first();
|
||||||
if ($admin_user) {
|
if ($admin_user) {
|
||||||
$data = $data->merge([
|
$data = $data->merge([
|
||||||
@@ -525,6 +566,15 @@ class Service extends BaseModel
|
|||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
if ($admin_email) {
|
||||||
|
$data = $data->merge([
|
||||||
|
'Email' => [
|
||||||
|
'key' => 'ADMIN_EMAIL',
|
||||||
|
'value' => data_get($admin_email, 'value'),
|
||||||
|
'rules' => 'required|email',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
}
|
||||||
$fields->put('Admin', $data->toArray());
|
$fields->put('Admin', $data->toArray());
|
||||||
break;
|
break;
|
||||||
case str($image)?->contains('vaultwarden'):
|
case str($image)?->contains('vaultwarden'):
|
||||||
|
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);
|
$server = Server::find($server_id);
|
||||||
$concurrent_builds = $server->settings->concurrent_builds;
|
$concurrent_builds = $server->settings->concurrent_builds;
|
||||||
|
|
||||||
ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}");
|
ray("serverId:{$server->id}", "concurrentBuilds:{$concurrent_builds}", "deployments:{$deployments->count()}", "sameApplicationDeployments:{$same_application_deployments->count()}")->green();
|
||||||
|
|
||||||
if ($deployments->count() > $concurrent_builds) {
|
if ($deployments->count() > $concurrent_builds) {
|
||||||
return false;
|
return false;
|
||||||
|
@@ -19,7 +19,7 @@ function generate_database_name(string $type): string
|
|||||||
return $type.'-database-'.$cuid;
|
return $type.'-database-'.$cuid;
|
||||||
}
|
}
|
||||||
|
|
||||||
function create_standalone_postgresql($environmentId, $destinationUuid, ?array $otherData = null): StandalonePostgresql
|
function create_standalone_postgresql($environmentId, $destinationUuid, ?array $otherData = null, string $databaseImage = 'postgres:16-alpine'): StandalonePostgresql
|
||||||
{
|
{
|
||||||
$destination = StandaloneDocker::where('uuid', $destinationUuid)->first();
|
$destination = StandaloneDocker::where('uuid', $destinationUuid)->first();
|
||||||
if (! $destination) {
|
if (! $destination) {
|
||||||
@@ -27,6 +27,7 @@ function create_standalone_postgresql($environmentId, $destinationUuid, ?array $
|
|||||||
}
|
}
|
||||||
$database = new StandalonePostgresql;
|
$database = new StandalonePostgresql;
|
||||||
$database->name = generate_database_name('postgresql');
|
$database->name = generate_database_name('postgresql');
|
||||||
|
$database->image = $databaseImage;
|
||||||
$database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
$database->postgres_password = \Illuminate\Support\Str::password(length: 64, symbols: false);
|
||||||
$database->environment_id = $environmentId;
|
$database->environment_id = $environmentId;
|
||||||
$database->destination_id = $destination->id;
|
$database->destination_id = $destination->id;
|
||||||
|
@@ -140,6 +140,8 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
|
|||||||
|
|
||||||
function generateApplicationContainerName(Application $application, $pull_request_id = 0)
|
function generateApplicationContainerName(Application $application, $pull_request_id = 0)
|
||||||
{
|
{
|
||||||
|
// TODO: refactor generateApplicationContainerName, we do not need $application and $pull_request_id
|
||||||
|
|
||||||
$consistent_container_name = $application->settings->is_consistent_container_name_enabled;
|
$consistent_container_name = $application->settings->is_consistent_container_name_enabled;
|
||||||
$now = now()->format('Hisu');
|
$now = now()->format('Hisu');
|
||||||
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
||||||
@@ -677,18 +679,19 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
|||||||
'--sysctl',
|
'--sysctl',
|
||||||
'--ulimit',
|
'--ulimit',
|
||||||
'--device',
|
'--device',
|
||||||
|
'--shm-size',
|
||||||
]);
|
]);
|
||||||
$mapping = collect([
|
$mapping = collect([
|
||||||
'--cap-add' => 'cap_add',
|
'--cap-add' => 'cap_add',
|
||||||
'--cap-drop' => 'cap_drop',
|
'--cap-drop' => 'cap_drop',
|
||||||
'--security-opt' => 'security_opt',
|
'--security-opt' => 'security_opt',
|
||||||
'--sysctl' => 'sysctls',
|
'--sysctl' => 'sysctls',
|
||||||
'--ulimit' => 'ulimits',
|
|
||||||
'--device' => 'devices',
|
'--device' => 'devices',
|
||||||
'--init' => 'init',
|
'--init' => 'init',
|
||||||
'--ulimit' => 'ulimits',
|
'--ulimit' => 'ulimits',
|
||||||
'--privileged' => 'privileged',
|
'--privileged' => 'privileged',
|
||||||
'--ip' => 'ip',
|
'--ip' => 'ip',
|
||||||
|
'--shm-size' => 'shm_size',
|
||||||
]);
|
]);
|
||||||
foreach ($matches as $match) {
|
foreach ($matches as $match) {
|
||||||
$option = $match[1];
|
$option = $match[1];
|
||||||
@@ -704,6 +707,7 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
|||||||
$options = collect($options);
|
$options = collect($options);
|
||||||
// Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js
|
// Easily get mappings from https://github.com/composerize/composerize/blob/master/packages/composerize/src/mappings.js
|
||||||
foreach ($options as $option => $value) {
|
foreach ($options as $option => $value) {
|
||||||
|
// ray($option,$value);
|
||||||
if (! data_get($mapping, $option)) {
|
if (! data_get($mapping, $option)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -728,6 +732,10 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
$compose_options->put($mapping[$option], $ulimits);
|
$compose_options->put($mapping[$option], $ulimits);
|
||||||
|
} elseif ($option === '--shm-size') {
|
||||||
|
if (! is_null($value) && is_array($value) && count($value) > 0) {
|
||||||
|
$compose_options->put($mapping[$option], $value[0]);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if ($list_options->contains($option)) {
|
if ($list_options->contains($option)) {
|
||||||
if ($compose_options->has($mapping[$option])) {
|
if ($compose_options->has($mapping[$option])) {
|
||||||
@@ -749,6 +757,26 @@ function convert_docker_run_to_compose(?string $custom_docker_run_options = null
|
|||||||
return $compose_options->toArray();
|
return $compose_options->toArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generate_custom_docker_run_options_for_databases($docker_run_options, $docker_compose, $container_name, $network)
|
||||||
|
{
|
||||||
|
$ipv4 = data_get($docker_run_options, 'ip.0');
|
||||||
|
$ipv6 = data_get($docker_run_options, 'ip6.0');
|
||||||
|
data_forget($docker_run_options, 'ip');
|
||||||
|
data_forget($docker_run_options, 'ip6');
|
||||||
|
if ($ipv4 || $ipv6) {
|
||||||
|
data_forget($docker_compose['services'][$container_name], 'networks');
|
||||||
|
}
|
||||||
|
if ($ipv4) {
|
||||||
|
$docker_compose['services'][$container_name]['networks'][$network]['ipv4_address'] = $ipv4;
|
||||||
|
}
|
||||||
|
if ($ipv6) {
|
||||||
|
$docker_compose['services'][$container_name]['networks'][$network]['ipv6_address'] = $ipv6;
|
||||||
|
}
|
||||||
|
$docker_compose['services'][$container_name] = array_merge_recursive($docker_compose['services'][$container_name], $docker_run_options);
|
||||||
|
|
||||||
|
return $docker_compose;
|
||||||
|
}
|
||||||
|
|
||||||
function validateComposeFile(string $compose, int $server_id): string|Throwable
|
function validateComposeFile(string $compose, int $server_id): string|Throwable
|
||||||
{
|
{
|
||||||
return 'OK';
|
return 'OK';
|
||||||
|
@@ -151,7 +151,7 @@ function generate_default_proxy_configuration(Server $server)
|
|||||||
'services' => [
|
'services' => [
|
||||||
'traefik' => [
|
'traefik' => [
|
||||||
'container_name' => 'coolify-proxy',
|
'container_name' => 'coolify-proxy',
|
||||||
'image' => 'traefik:v2.11',
|
'image' => 'traefik:v3.1',
|
||||||
'restart' => RESTART_MODE,
|
'restart' => RESTART_MODE,
|
||||||
'extra_hosts' => [
|
'extra_hosts' => [
|
||||||
'host.docker.internal:host-gateway',
|
'host.docker.internal:host-gateway',
|
||||||
|
@@ -170,7 +170,7 @@ function generateSshCommand(Server $server, string $command)
|
|||||||
// ray($ssh_command);
|
// ray($ssh_command);
|
||||||
return $ssh_command;
|
return $ssh_command;
|
||||||
}
|
}
|
||||||
function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false)
|
function instant_remote_process(Collection|array $command, Server $server, bool $throwError = true, bool $no_sudo = false): ?string
|
||||||
{
|
{
|
||||||
$timeout = config('constants.ssh.command_timeout');
|
$timeout = config('constants.ssh.command_timeout');
|
||||||
if ($command instanceof Collection) {
|
if ($command instanceof Collection) {
|
||||||
|
@@ -53,7 +53,9 @@ function getFilesystemVolumesFromServer(ServiceApplication|ServiceDatabase|Appli
|
|||||||
if ($isFile == 'OK') {
|
if ($isFile == 'OK') {
|
||||||
// If its a file & exists
|
// If its a file & exists
|
||||||
$filesystemContent = instant_remote_process(["cat $fileLocation"], $server);
|
$filesystemContent = instant_remote_process(["cat $fileLocation"], $server);
|
||||||
$fileVolume->content = $filesystemContent;
|
if ($fileVolume->is_based_on_git) {
|
||||||
|
$fileVolume->content = $filesystemContent;
|
||||||
|
}
|
||||||
$fileVolume->is_directory = false;
|
$fileVolume->is_directory = false;
|
||||||
$fileVolume->save();
|
$fileVolume->save();
|
||||||
} elseif ($isDir == 'OK') {
|
} elseif ($isDir == 'OK') {
|
||||||
|
@@ -18,6 +18,7 @@
|
|||||||
"laravel/prompts": "^0.1.6",
|
"laravel/prompts": "^0.1.6",
|
||||||
"laravel/sanctum": "^v4.0",
|
"laravel/sanctum": "^v4.0",
|
||||||
"laravel/socialite": "^v5.14.0",
|
"laravel/socialite": "^v5.14.0",
|
||||||
|
"laravel/telescope": "^5.2",
|
||||||
"laravel/tinker": "^v2.8.1",
|
"laravel/tinker": "^v2.8.1",
|
||||||
"laravel/ui": "^4.2",
|
"laravel/ui": "^4.2",
|
||||||
"lcobucci/jwt": "^5.0.0",
|
"lcobucci/jwt": "^5.0.0",
|
||||||
@@ -93,7 +94,9 @@
|
|||||||
},
|
},
|
||||||
"extra": {
|
"extra": {
|
||||||
"laravel": {
|
"laravel": {
|
||||||
"dont-discover": []
|
"dont-discover": [
|
||||||
|
"laravel/telescope"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"config": {
|
"config": {
|
||||||
|
71
composer.lock
generated
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "cb17445966de6094aef5a92ee59d1d77",
|
"content-hash": "213fa37e1cc796508e32069c26f3c84a",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "amphp/amp",
|
"name": "amphp/amp",
|
||||||
@@ -3388,6 +3388,75 @@
|
|||||||
},
|
},
|
||||||
"time": "2024-06-28T20:09:34+00:00"
|
"time": "2024-06-28T20:09:34+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "laravel/telescope",
|
||||||
|
"version": "v5.2.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/laravel/telescope.git",
|
||||||
|
"reference": "c3a794ae6e011c8410e4de7c49ddab079ab846b0"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/laravel/telescope/zipball/c3a794ae6e011c8410e4de7c49ddab079ab846b0",
|
||||||
|
"reference": "c3a794ae6e011c8410e4de7c49ddab079ab846b0",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"ext-json": "*",
|
||||||
|
"laravel/framework": "^8.37|^9.0|^10.0|^11.0",
|
||||||
|
"php": "^8.0",
|
||||||
|
"symfony/console": "^5.3|^6.0|^7.0",
|
||||||
|
"symfony/var-dumper": "^5.0|^6.0|^7.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"ext-gd": "*",
|
||||||
|
"guzzlehttp/guzzle": "^6.0|^7.0",
|
||||||
|
"laravel/octane": "^1.4|^2.0|dev-develop",
|
||||||
|
"orchestra/testbench": "^6.40|^7.37|^8.17|^9.0",
|
||||||
|
"phpstan/phpstan": "^1.10",
|
||||||
|
"phpunit/phpunit": "^9.0|^10.5"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"laravel": {
|
||||||
|
"providers": [
|
||||||
|
"Laravel\\Telescope\\TelescopeServiceProvider"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Laravel\\Telescope\\": "src/",
|
||||||
|
"Laravel\\Telescope\\Database\\Factories\\": "database/factories/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Taylor Otwell",
|
||||||
|
"email": "taylor@laravel.com"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Mohamed Said",
|
||||||
|
"email": "mohamed@laravel.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"description": "An elegant debug assistant for the Laravel framework.",
|
||||||
|
"keywords": [
|
||||||
|
"debugging",
|
||||||
|
"laravel",
|
||||||
|
"monitoring"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/laravel/telescope/issues",
|
||||||
|
"source": "https://github.com/laravel/telescope/tree/v5.2.1"
|
||||||
|
},
|
||||||
|
"time": "2024-08-20T14:54:41+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "laravel/tinker",
|
"name": "laravel/tinker",
|
||||||
"version": "v2.9.0",
|
"version": "v2.9.0",
|
||||||
|
@@ -199,6 +199,7 @@ return [
|
|||||||
App\Providers\EventServiceProvider::class,
|
App\Providers\EventServiceProvider::class,
|
||||||
App\Providers\HorizonServiceProvider::class,
|
App\Providers\HorizonServiceProvider::class,
|
||||||
App\Providers\RouteServiceProvider::class,
|
App\Providers\RouteServiceProvider::class,
|
||||||
|
App\Providers\TelescopeServiceProvider::class,
|
||||||
|
|
||||||
],
|
],
|
||||||
|
|
||||||
|
@@ -7,7 +7,7 @@ return [
|
|||||||
|
|
||||||
// The release version of your application
|
// The release version of your application
|
||||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||||
'release' => '4.0.0-beta.323',
|
'release' => '4.0.0-beta.324',
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
|
||||||
|
206
config/telescope.php
Normal file
@@ -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
|
<?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,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
|
public function run(): void
|
||||||
{
|
{
|
||||||
StandaloneDocker::create([
|
if (StandaloneDocker::find(0) == null) {
|
||||||
'id' => 0,
|
StandaloneDocker::create([
|
||||||
'name' => 'Standalone Docker 1',
|
'id' => 0,
|
||||||
'network' => 'coolify',
|
'name' => 'Standalone Docker 1',
|
||||||
'server_id' => 0,
|
'network' => 'coolify',
|
||||||
]);
|
'server_id' => 0,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -247,13 +247,13 @@ paths:
|
|||||||
security:
|
security:
|
||||||
-
|
-
|
||||||
bearerAuth: []
|
bearerAuth: []
|
||||||
/applications/private-gh-app:
|
/applications/private-github-app:
|
||||||
post:
|
post:
|
||||||
tags:
|
tags:
|
||||||
- Applications
|
- Applications
|
||||||
summary: 'Create (Private - GH App)'
|
summary: 'Create (Private - GH App)'
|
||||||
description: 'Create new application based on a private repository through a Github App.'
|
description: 'Create new application based on a private repository through a Github App.'
|
||||||
operationId: 4d46c84bda4f1a411f6dda15fce4061f
|
operationId: 8b7af9c9a509385963bf3e72eeeea786
|
||||||
requestBody:
|
requestBody:
|
||||||
description: 'Application object that needs to be created.'
|
description: 'Application object that needs to be created.'
|
||||||
required: true
|
required: true
|
||||||
|
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="flex flex-col justify-center flex-1">
|
||||||
<div class="box-title">{{ $project->name }}</div>
|
<div class="box-title">{{ $project->name }}</div>
|
||||||
<div class="box-description">
|
<div class="box-description">
|
||||||
{{ $project->description }}</div>
|
{{ $project->description }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<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"
|
<a class="hover:underline"
|
||||||
href="{{ route('project.resource.create', ['project_uuid' => data_get($project, 'uuid'), 'environment_name' => data_get($project, 'environments.0.name', 'production')]) }}">
|
href="{{ route('project.resource.create', ['project_uuid' => $project->uuid, 'environment_name' => data_get($project, 'default_environment()', 'production')]) }}">
|
||||||
<span class="p-2 font-bold">+
|
<span class="p-2 font-bold">+ Add Resource</span>
|
||||||
Add Resource</span>
|
|
||||||
</a>
|
</a>
|
||||||
<a class="hover:underline"
|
<a class="hover:underline"
|
||||||
href="{{ route('project.edit', ['project_uuid' => data_get($project, 'uuid')]) }}">
|
href="{{ route('project.edit', ['project_uuid' => $project->uuid]) }}">
|
||||||
Settings
|
Settings
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -159,12 +159,14 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function gotoProject(uuid, environment) {
|
function gotoProject(uuid, environment) {
|
||||||
if (!environment) {
|
if (environment) {
|
||||||
|
window.location.href = '/project/' + uuid + '/' + environment;
|
||||||
|
} else {
|
||||||
window.location.href = '/project/' + uuid;
|
window.location.href = '/project/' + uuid;
|
||||||
}
|
}
|
||||||
window.location.href = '/project/' + uuid + '/' + environment;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
{{-- <x-forms.button wire:click='getIptables'>Get IPTABLES</x-forms.button> --}}
|
{{-- <x-forms.button wire:click='getIptables'>Get IPTABLES</x-forms.button> --}}
|
||||||
|
@@ -254,6 +254,11 @@
|
|||||||
helper="You need to modify the docker compose file." monacoEditorLanguage="yaml"
|
helper="You need to modify the docker compose file." monacoEditorLanguage="yaml"
|
||||||
useMonacoEditor />
|
useMonacoEditor />
|
||||||
@else
|
@else
|
||||||
|
@if ($application->compose_parsing_version === '3')
|
||||||
|
<x-forms.textarea rows="10" readonly id="application.docker_compose_raw"
|
||||||
|
label="Docker Compose Content (raw)" helper="You need to modify the docker compose file."
|
||||||
|
monacoEditorLanguage="yaml" useMonacoEditor />
|
||||||
|
@endif
|
||||||
<x-forms.textarea rows="10" readonly id="application.docker_compose"
|
<x-forms.textarea rows="10" readonly id="application.docker_compose"
|
||||||
label="Docker Compose Content" helper="You need to modify the docker compose file."
|
label="Docker Compose Content" helper="You need to modify the docker compose file."
|
||||||
monacoEditorLanguage="yaml" useMonacoEditor />
|
monacoEditorLanguage="yaml" useMonacoEditor />
|
||||||
|
@@ -21,14 +21,18 @@
|
|||||||
readonly helper="You can only change this in the database." />
|
readonly helper="You can only change this in the database." />
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="pt-8 dark:text-warning">Please verify these values. You can only modify them before the initial
|
<div class=" dark:text-warning">Please verify these values. You can only modify them before the initial
|
||||||
start. After that, you need to modify it in the database.
|
start. After that, you need to modify it in the database.
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2 pb-8">
|
<div class="flex gap-2">
|
||||||
<x-forms.input label="Username" id="database.clickhouse_admin_user" required />
|
<x-forms.input label="Username" id="database.clickhouse_admin_user" required />
|
||||||
<x-forms.input label="Password" id="database.clickhouse_admin_password" type="password" required />
|
<x-forms.input label="Password" id="database.clickhouse_admin_password" type="password" required />
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
<x-forms.input
|
||||||
|
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
||||||
|
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
|
||||||
|
id="database.custom_docker_run_options" label="Custom Docker Options" />
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<h3 class="py-2">Network</h3>
|
<h3 class="py-2">Network</h3>
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
|
@@ -11,6 +11,10 @@
|
|||||||
<x-forms.input label="Description" id="database.description" />
|
<x-forms.input label="Description" id="database.description" />
|
||||||
<x-forms.input label="Image" id="database.image" required />
|
<x-forms.input label="Image" id="database.image" required />
|
||||||
</div>
|
</div>
|
||||||
|
<x-forms.input
|
||||||
|
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
||||||
|
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
|
||||||
|
id="database.custom_docker_run_options" label="Custom Docker Options" />
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<h3 class="py-2">Network</h3>
|
<h3 class="py-2">Network</h3>
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
|
@@ -12,6 +12,10 @@
|
|||||||
<x-forms.input label="Image" id="database.image" required
|
<x-forms.input label="Image" id="database.image" required
|
||||||
helper="For all available images, check here:<br><br><a target='_blank' href=https://hub.docker.com/r/eqalpha/keydb'>https://hub.docker.com/r/eqalpha/keydb</a>" />
|
helper="For all available images, check here:<br><br><a target='_blank' href=https://hub.docker.com/r/eqalpha/keydb'>https://hub.docker.com/r/eqalpha/keydb</a>" />
|
||||||
</div>
|
</div>
|
||||||
|
<x-forms.input
|
||||||
|
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
||||||
|
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
|
||||||
|
id="database.custom_docker_run_options" label="Custom Docker Options" />
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<h3 class="py-2">Network</h3>
|
<h3 class="py-2">Network</h3>
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
|
@@ -16,30 +16,40 @@
|
|||||||
automations (like backups) won't work.
|
automations (like backups) won't work.
|
||||||
</div>
|
</div>
|
||||||
@if ($database->started_at)
|
@if ($database->started_at)
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex xl:flex-row flex-col gap-2">
|
||||||
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password" required
|
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password" required
|
||||||
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
||||||
<x-forms.input label="Normal User" id="database.mariadb_user" required
|
<x-forms.input label="Normal User" id="database.mariadb_user" required
|
||||||
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
||||||
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
|
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
|
||||||
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
<x-forms.input label="Initial Database" id="database.mariadb_database"
|
<x-forms.input label="Initial Database" id="database.mariadb_database"
|
||||||
placeholder="If empty, it will be the same as Username." readonly
|
placeholder="If empty, it will be the same as Username." readonly
|
||||||
helper="You can only change this in the database." />
|
helper="You can only change this in the database." />
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="flex flex-col gap-2 pb-2">
|
<div class="flex xl:flex-row flex-col gap-2 pb-2">
|
||||||
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password"
|
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password"
|
||||||
helper="You can only change this in the database." />
|
helper="You can only change this in the database." />
|
||||||
<x-forms.input label="Normal User" id="database.mariadb_user" required
|
<x-forms.input label="Normal User" id="database.mariadb_user" required
|
||||||
helper="You can only change this in the database." />
|
helper="You can only change this in the database." />
|
||||||
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
|
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
|
||||||
helper="You can only change this in the database." />
|
helper="You can only change this in the database." />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
<x-forms.input label="Initial Database" id="database.mariadb_database"
|
<x-forms.input label="Initial Database" id="database.mariadb_database"
|
||||||
placeholder="If empty, it will be the same as Username."
|
placeholder="If empty, it will be the same as Username."
|
||||||
helper="You can only change this in the database." />
|
helper="You can only change this in the database." />
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
<div class="pt-2">
|
||||||
|
<x-forms.input
|
||||||
|
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
||||||
|
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
|
||||||
|
id="database.custom_docker_run_options" label="Custom Docker Options" />
|
||||||
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<h3 class="py-2">Network</h3>
|
<h3 class="py-2">Network</h3>
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
|
@@ -16,7 +16,7 @@
|
|||||||
automations (like backups) won't work.
|
automations (like backups) won't work.
|
||||||
</div>
|
</div>
|
||||||
@if ($database->started_at)
|
@if ($database->started_at)
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex xl:flex-row flex-col gap-2">
|
||||||
<x-forms.input label="Initial Username" id="database.mongo_initdb_root_username"
|
<x-forms.input label="Initial Username" id="database.mongo_initdb_root_username"
|
||||||
placeholder="If empty: postgres"
|
placeholder="If empty: postgres"
|
||||||
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
helper="You can only change this in the database." />
|
helper="You can only change this in the database." />
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="flex flex-col gap-2 pb-2">
|
<div class="flex xl:flex-row flex-col gap-2 pb-2">
|
||||||
<x-forms.input required label="Username" id="database.mongo_initdb_root_username"
|
<x-forms.input required label="Username" id="database.mongo_initdb_root_username"
|
||||||
placeholder="If empty: postgres" />
|
placeholder="If empty: postgres" />
|
||||||
<x-forms.input label="Password" id="database.mongo_initdb_root_password" type="password" required />
|
<x-forms.input label="Password" id="database.mongo_initdb_root_password" type="password" required />
|
||||||
@@ -36,6 +36,10 @@
|
|||||||
placeholder="If empty, it will be the same as Username." />
|
placeholder="If empty, it will be the same as Username." />
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
<x-forms.input
|
||||||
|
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
||||||
|
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
|
||||||
|
id="database.custom_docker_run_options" label="Custom Docker Options" />
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<h3 class="py-2">Network</h3>
|
<h3 class="py-2">Network</h3>
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
|
@@ -16,30 +16,40 @@
|
|||||||
automations (like backups) won't work.
|
automations (like backups) won't work.
|
||||||
</div>
|
</div>
|
||||||
@if ($database->started_at)
|
@if ($database->started_at)
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex xl:flex-row flex-col gap-2">
|
||||||
<x-forms.input label="Root Password" id="database.mysql_root_password" type="password" required
|
<x-forms.input label="Root Password" id="database.mysql_root_password" type="password" required
|
||||||
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
||||||
<x-forms.input label="Normal User" id="database.mysql_user" required
|
<x-forms.input label="Normal User" id="database.mysql_user" required
|
||||||
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
||||||
<x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required
|
<x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required
|
||||||
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
<x-forms.input label="Initial Database" id="database.mysql_database"
|
<x-forms.input label="Initial Database" id="database.mysql_database"
|
||||||
placeholder="If empty, it will be the same as Username." readonly
|
placeholder="If empty, it will be the same as Username." readonly
|
||||||
helper="You can only change this in the database." />
|
helper="You can only change this in the database." />
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="flex flex-col gap-4 pb-2">
|
<div class="flex xl:flex-row flex-col gap-4 pb-2">
|
||||||
<x-forms.input label="Root Password" id="database.mysql_root_password" type="password"
|
<x-forms.input label="Root Password" id="database.mysql_root_password" type="password"
|
||||||
helper="You can only change this in the database." />
|
helper="You can only change this in the database." />
|
||||||
<x-forms.input label="Normal User" id="database.mysql_user" required
|
<x-forms.input label="Normal User" id="database.mysql_user" required
|
||||||
helper="You can only change this in the database." />
|
helper="You can only change this in the database." />
|
||||||
<x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required
|
<x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required
|
||||||
helper="You can only change this in the database." />
|
helper="You can only change this in the database." />
|
||||||
|
</div>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
<x-forms.input label="Initial Database" id="database.mysql_database"
|
<x-forms.input label="Initial Database" id="database.mysql_database"
|
||||||
placeholder="If empty, it will be the same as Username."
|
placeholder="If empty, it will be the same as Username."
|
||||||
helper="You can only change this in the database." />
|
helper="You can only change this in the database." />
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
<div class="pt-2">
|
||||||
|
<x-forms.input
|
||||||
|
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
||||||
|
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
|
||||||
|
id="database.custom_docker_run_options" label="Custom Docker Options" />
|
||||||
|
</div>
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<h3 class="py-2">Network</h3>
|
<h3 class="py-2">Network</h3>
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
|
@@ -30,7 +30,7 @@
|
|||||||
automations (like backups) won't work.
|
automations (like backups) won't work.
|
||||||
</div>
|
</div>
|
||||||
@if ($database->started_at)
|
@if ($database->started_at)
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex xl:flex-row flex-col gap-2">
|
||||||
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres"
|
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres"
|
||||||
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
|
||||||
<x-forms.input label="Password" id="database.postgres_password" type="password" required
|
<x-forms.input label="Password" id="database.postgres_password" type="password" required
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
helper="You can only change this in the database." />
|
helper="You can only change this in the database." />
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<div class="flex flex-col gap-2 pb-2">
|
<div class="flex xl:flex-row flex-col gap-2 pb-2">
|
||||||
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres" />
|
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres" />
|
||||||
<x-forms.input label="Password" id="database.postgres_password" type="password" required />
|
<x-forms.input label="Password" id="database.postgres_password" type="password" required />
|
||||||
<x-forms.input label="Initial Database" id="database.postgres_db"
|
<x-forms.input label="Initial Database" id="database.postgres_db"
|
||||||
@@ -53,6 +53,10 @@
|
|||||||
<x-forms.input label="Host Auth Method" id="database.postgres_host_auth_method"
|
<x-forms.input label="Host Auth Method" id="database.postgres_host_auth_method"
|
||||||
placeholder="If empty, use default. See in docker docs." />
|
placeholder="If empty, use default. See in docker docs." />
|
||||||
</div>
|
</div>
|
||||||
|
<x-forms.input
|
||||||
|
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
||||||
|
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
|
||||||
|
id="database.custom_docker_run_options" label="Custom Docker Options" />
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<h3 class="py-2">Network</h3>
|
<h3 class="py-2">Network</h3>
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
|
@@ -12,6 +12,10 @@
|
|||||||
<x-forms.input label="Image" id="database.image" required
|
<x-forms.input label="Image" id="database.image" required
|
||||||
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/redis'>https://hub.docker.com/_/redis</a>" />
|
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/redis'>https://hub.docker.com/_/redis</a>" />
|
||||||
</div>
|
</div>
|
||||||
|
<x-forms.input
|
||||||
|
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
|
||||||
|
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
|
||||||
|
id="database.custom_docker_run_options" label="Custom Docker Options" />
|
||||||
<div class="flex flex-col gap-2">
|
<div class="flex flex-col gap-2">
|
||||||
<h3 class="py-2">Network</h3>
|
<h3 class="py-2">Network</h3>
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
|
@@ -11,9 +11,8 @@
|
|||||||
<div class="subtitle">All your projects are here.</div>
|
<div class="subtitle">All your projects are here.</div>
|
||||||
<div class="grid gap-2 lg:grid-cols-2">
|
<div class="grid gap-2 lg:grid-cols-2">
|
||||||
@forelse ($projects as $project)
|
@forelse ($projects as $project)
|
||||||
<div class="box group" x-data x-on:click="goto('{{ $project->uuid }}')">
|
<div class="box group" onclick="gotoProject('{{ $project->uuid }}', '{{ $project->default_environment() }}')">
|
||||||
<div class="flex flex-col justify-center flex-1 mx-6"
|
<div class="flex flex-col justify-center flex-1 mx-6">
|
||||||
onclick="gotoProject('{{ $project->uuid }}','{{ $project->default_environment() }}')">
|
|
||||||
<div class="box-title">{{ $project->name }}</div>
|
<div class="box-title">{{ $project->name }}</div>
|
||||||
<div class="box-description ">
|
<div class="box-description ">
|
||||||
{{ $project->description }}</div>
|
{{ $project->description }}</div>
|
||||||
@@ -33,15 +32,12 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
function goto(uuid) {
|
function gotoProject(uuid, environment) {
|
||||||
|
if (environment) {
|
||||||
|
window.location.href = '/project/' + uuid + '/' + environment;
|
||||||
|
} else {
|
||||||
window.location.href = '/project/' + uuid;
|
window.location.href = '/project/' + uuid;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
function gotoProject(uuid, environment) {
|
</script>
|
||||||
if (!environment) {
|
|
||||||
window.location.href = '/project/' + uuid;
|
|
||||||
}
|
|
||||||
window.location.href = '/project/' + uuid + '/' + environment;
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
</div>
|
</div>
|
||||||
|
@@ -617,6 +617,85 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
@if ($current_step === 'select-postgresql-type')
|
||||||
|
<h2>Select a Postgresql type</h2>
|
||||||
|
<div>If you need extra extensions, you can select Supabase PostgreSQL (or others), otherwise select PostgreSQL
|
||||||
|
16 (default).</div>
|
||||||
|
<div class="flex flex-col gap-4">
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<div class="gap-2 border border-transparent cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
|
||||||
|
wire:click="setPostgresqlType('postgres:16-alpine')">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="box-title">PostgreSQL 16 (default)</div>
|
||||||
|
<div class="box-description">
|
||||||
|
PostgreSQL is a powerful, open-source object-relational database system (no extensions).
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1"></div>
|
||||||
|
|
||||||
|
<div class="flex items-center px-2" title="Read the documentation.">
|
||||||
|
<a class="p-2 hover:underline group-hover:dark:text-white dark:text-white text-neutral-6000"
|
||||||
|
onclick="event.stopPropagation()" href="https://hub.docker.com/_/postgres/"
|
||||||
|
target="_blank">
|
||||||
|
Documentation
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="gap-2 border border-transparent cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
|
||||||
|
wire:click="setPostgresqlType('supabase/postgres:15.6.1.113')">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="box-title">Supabase PostgreSQL (with extensions)</div>
|
||||||
|
<div class="box-description">
|
||||||
|
Supabase is a modern, open-source alternative to PostgreSQL with lots of extensions.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1"></div>
|
||||||
|
<div class="flex items-center px-2" title="Read the documentation.">
|
||||||
|
<a class="p-2 hover:underline group-hover:dark:text-white dark:text-white text-neutral-600"
|
||||||
|
onclick="event.stopPropagation()" href="https://github.com/supabase/postgres"
|
||||||
|
target="_blank">
|
||||||
|
Documentation
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="gap-2 border border-transparent cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
|
||||||
|
wire:click="setPostgresqlType('postgis/postgis')">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="box-title">PostGIS</div>
|
||||||
|
<div class="box-description">
|
||||||
|
PostGIS is a PostgreSQL extension for geographic objects.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1"></div>
|
||||||
|
<div class="flex items-center px-2" title="Read the documentation.">
|
||||||
|
<a class="p-2 hover:underline group-hover:dark:text-white dark:text-white text-neutral-600"
|
||||||
|
onclick="event.stopPropagation()" href="https://github.com/postgis/docker-postgis"
|
||||||
|
target="_blank">
|
||||||
|
Documentation
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="gap-2 border border-transparent cursor-pointer box-without-bg dark:bg-coolgray-100 bg-white dark:hover:text-neutral-400 dark:hover:bg-coollabs group flex"
|
||||||
|
wire:click="setPostgresqlType('pgvector/pgvector:pg16')">
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<div class="box-title">PGVector (16)</div>
|
||||||
|
<div class="box-description">
|
||||||
|
PGVector is a PostgreSQL extension for vector data types.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1"></div>
|
||||||
|
|
||||||
|
<div class="flex items-center px-2" title="Read the documentation.">
|
||||||
|
<a class="p-2 hover:underline group-hover:dark:text-white dark:text-white text-neutral-600"
|
||||||
|
onclick="event.stopPropagation()" href="https://github.com/pgvector/pgvector"
|
||||||
|
target="_blank">
|
||||||
|
Documentation
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
@if ($current_step === 'existing-postgresql')
|
@if ($current_step === 'existing-postgresql')
|
||||||
<form wire:submit='addExistingPostgresql' class="flex items-end gap-4">
|
<form wire:submit='addExistingPostgresql' class="flex items-end gap-4">
|
||||||
<x-forms.input placeholder="postgres://username:password@database:5432" label="Database URL"
|
<x-forms.input placeholder="postgres://username:password@database:5432" label="Database URL"
|
||||||
|
@@ -1,14 +1,16 @@
|
|||||||
<div class="p-4 transition border rounded dark:border-coolgray-200">
|
<div class="py-4 ">
|
||||||
<div class="flex flex-col justify-center pb-4 text-sm select-text">
|
<div class="flex flex-col justify-center pb-4 text-sm select-text">
|
||||||
@if (data_get($resource, 'build_pack') === 'dockercompose')
|
{{-- @if (data_get($resource, 'build_pack') === 'dockercompose')
|
||||||
<h2>{{ data_get($resource, 'name', 'unknown') }}</h2>
|
<h4>{{ data_get($resource, 'name', 'unknown') }}</h4>
|
||||||
@endif
|
@endif --}}
|
||||||
@if ($fileStorage->is_directory)
|
@if ($fileStorage->is_directory)
|
||||||
<div class="dark:text-white">Directory Mount</div>
|
<h4 class="dark:text-white">Directory Mount</h4>
|
||||||
@else
|
@else
|
||||||
<div class="dark:text-white">File Mount</div>
|
<h4 class="dark:text-white">File Mount</h4>
|
||||||
@endif
|
@endif
|
||||||
<div>{{ $workdir }}{{ $fs_path }} -> {{ $fileStorage->mount_path }}</div>
|
|
||||||
|
<x-forms.input label="Source Path" :value="$fileStorage->fs_path" readonly />
|
||||||
|
<x-forms.input label="Destination Path" :value="$fileStorage->mount_path" readonly />
|
||||||
</div>
|
</div>
|
||||||
<form wire:submit='submit' class="flex flex-col gap-2">
|
<form wire:submit='submit' class="flex flex-col gap-2">
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
@@ -26,24 +28,38 @@
|
|||||||
</div>
|
</div>
|
||||||
</x-modal-confirmation>
|
</x-modal-confirmation>
|
||||||
@endif
|
@endif
|
||||||
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
|
||||||
<div class="px-2">This storage will be deleted. It is not reversible. <strong
|
@if (!$fileStorage->is_based_on_git)
|
||||||
class="text-error">Please
|
<x-modal-confirmation isErrorButton buttonTitle="Delete">
|
||||||
think
|
<div class="px-2">This storage will be deleted. It is not reversible. <strong
|
||||||
again.</strong><br><br></div>
|
class="text-error">Please
|
||||||
<h4>Actions</h4>
|
think
|
||||||
@if ($fileStorage->is_directory)
|
again.</strong><br><br></div>
|
||||||
<x-forms.checkbox id="permanently_delete"
|
<h4>Actions</h4>
|
||||||
label="Permanently delete directory from the server?"></x-forms.checkbox>
|
@if ($fileStorage->is_directory)
|
||||||
@else
|
<x-forms.checkbox id="permanently_delete"
|
||||||
<x-forms.checkbox id="permanently_delete"
|
label="Permanently delete directory from the server?"></x-forms.checkbox>
|
||||||
label="Permanently delete file from the server?"></x-forms.checkbox>
|
@else
|
||||||
@endif
|
<x-forms.checkbox id="permanently_delete"
|
||||||
</x-modal-confirmation>
|
label="Permanently delete file from the server?"></x-forms.checkbox>
|
||||||
|
@endif
|
||||||
|
</x-modal-confirmation>
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@if (!$fileStorage->is_directory)
|
@if (!$fileStorage->is_directory)
|
||||||
<x-forms.textarea label="Content" rows="20" id="fileStorage.content"></x-forms.textarea>
|
@if (data_get($resource, 'settings.is_preserve_repository_enabled'))
|
||||||
<x-forms.button class="w-full" type="submit">Save</x-forms.button>
|
<div class="w-96">
|
||||||
|
<x-forms.checkbox instantSave label="Is this based on the Git repository?"
|
||||||
|
id="fileStorage.is_based_on_git"></x-forms.checkbox>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
<x-forms.textarea
|
||||||
|
label="{{ $fileStorage->is_based_on_git ? 'Content (refreshed after a successful deployment)' : 'Content' }}"
|
||||||
|
rows="20" id="fileStorage.content"
|
||||||
|
readonly="{{ $fileStorage->is_based_on_git }}"></x-forms.textarea>
|
||||||
|
@if (!$fileStorage->is_based_on_git)
|
||||||
|
<x-forms.button class="w-full" type="submit">Save</x-forms.button>
|
||||||
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
@@ -31,10 +31,13 @@
|
|||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if ($resource->persistentStorages()->get()->count() > 0)
|
@if ($resource->persistentStorages()->get()->count() > 0)
|
||||||
|
<h3 class="pt-4">Volumes</h3>
|
||||||
<livewire:project.shared.storages.all :resource="$resource" />
|
<livewire:project.shared.storages.all :resource="$resource" />
|
||||||
@endif
|
@endif
|
||||||
@if ($fileStorage->count() > 0)
|
@if ($fileStorage->count() > 0)
|
||||||
<div class="flex flex-col gap-4 pt-4">
|
|
||||||
|
<h3 class="mt-4 pt-2 border-t dark:border-coolgray-200">Mounts</h3>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
@foreach ($fileStorage->sort() as $fileStorage)
|
@foreach ($fileStorage->sort() as $fileStorage)
|
||||||
<livewire:project.service.file-storage :fileStorage="$fileStorage"
|
<livewire:project.service.file-storage :fileStorage="$fileStorage"
|
||||||
wire:key="resource-{{ $fileStorage->uuid }}" />
|
wire:key="resource-{{ $fileStorage->uuid }}" />
|
||||||
|
@@ -19,9 +19,6 @@
|
|||||||
<div class="box-description">
|
<div class="box-description">
|
||||||
Network: {{ data_get($resource, 'destination.network') }}
|
Network: {{ data_get($resource, 'destination.network') }}
|
||||||
</div>
|
</div>
|
||||||
@if ($resource->server_status == false)
|
|
||||||
<div class="text-xs font-bold text-error"> This server has connection problems. </div>
|
|
||||||
@endif
|
|
||||||
</div>
|
</div>
|
||||||
@if ($resource?->additional_networks?->count() > 0)
|
@if ($resource?->additional_networks?->count() > 0)
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
|
@@ -2,9 +2,11 @@
|
|||||||
<div>
|
<div>
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h2>Environment Variables</h2>
|
<h2>Environment Variables</h2>
|
||||||
<x-modal-input buttonTitle="+ Add" title="New Environment Variable">
|
<div class="flex flex-col items-center">
|
||||||
<livewire:project.shared.environment-variable.add />
|
<x-modal-input buttonTitle="+ Add" title="New Environment Variable">
|
||||||
</x-modal-input>
|
<livewire:project.shared.environment-variable.add />
|
||||||
|
</x-modal-input>
|
||||||
|
</div>
|
||||||
<x-forms.button
|
<x-forms.button
|
||||||
wire:click='switch'>{{ $view === 'normal' ? 'Developer view' : 'Normal view' }}</x-forms.button>
|
wire:click='switch'>{{ $view === 'normal' ? 'Developer view' : 'Normal view' }}</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
@@ -12,7 +14,7 @@
|
|||||||
@if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose')
|
@if ($this->resourceClass === 'App\Models\Application' && data_get($this->resource, 'build_pack') !== 'dockercompose')
|
||||||
<div class="w-64 pt-2">
|
<div class="w-64 pt-2">
|
||||||
<x-forms.checkbox id="resource.settings.is_env_sorting_enabled" label="Sort alphabetically"
|
<x-forms.checkbox id="resource.settings.is_env_sorting_enabled" label="Sort alphabetically"
|
||||||
helper="Turn this off if one environment is dependent on an other. It will be sorted by creation order."
|
helper="Turn this off if one environment is dependent on an other. It will be sorted by creation order (like you pasted them or in the order you created them)."
|
||||||
instantSave></x-forms.checkbox>
|
instantSave></x-forms.checkbox>
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@@ -31,6 +33,10 @@
|
|||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@if ($view === 'normal')
|
@if ($view === 'normal')
|
||||||
|
<div>
|
||||||
|
<h3>Production Environment Variables</h3>
|
||||||
|
<div>Environment (secrets) variables for Production.</div>
|
||||||
|
</div>
|
||||||
@forelse ($resource->environment_variables as $env)
|
@forelse ($resource->environment_variables as $env)
|
||||||
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
|
<livewire:project.shared.environment-variable.show wire:key="environment-{{ $env->id }}"
|
||||||
:env="$env" :type="$resource->type()" />
|
:env="$env" :type="$resource->type()" />
|
||||||
@@ -39,7 +45,7 @@
|
|||||||
@endforelse
|
@endforelse
|
||||||
@if ($resource->type() === 'application' && $resource->environment_variables_preview->count() > 0 && $showPreview)
|
@if ($resource->type() === 'application' && $resource->environment_variables_preview->count() > 0 && $showPreview)
|
||||||
<div>
|
<div>
|
||||||
<h3>Preview Deployments</h3>
|
<h3>Preview Deployments Environment Variables</h3>
|
||||||
<div>Environment (secrets) variables for Preview Deployments.</div>
|
<div>Environment (secrets) variables for Preview Deployments.</div>
|
||||||
</div>
|
</div>
|
||||||
@foreach ($resource->environment_variables_preview as $env)
|
@foreach ($resource->environment_variables_preview as $env)
|
||||||
@@ -48,16 +54,15 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
@endif
|
@endif
|
||||||
@else
|
@else
|
||||||
<form wire:submit='saveVariables(false)' class="flex flex-col gap-2">
|
<form wire:submit.prevent='submit' class="flex flex-col gap-2">
|
||||||
<x-forms.textarea rows="10" class="whitespace-pre-wrap" id="variables"></x-forms.textarea>
|
<x-forms.textarea rows="10" class="whitespace-pre-wrap" id="variables" wire:model="variables" label="Production Environment Variables"></x-forms.textarea>
|
||||||
<x-forms.button type="submit" class="btn btn-primary">Save</x-forms.button>
|
|
||||||
|
@if ($showPreview)
|
||||||
|
<x-forms.textarea rows="10" class="whitespace-pre-wrap" label="Preview Deployments Environment Variables"
|
||||||
|
id="variablesPreview" wire:model="variablesPreview"></x-forms.textarea>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
<x-forms.button type="submit" class="btn btn-primary">Save All Environment Variables</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
@if ($showPreview)
|
|
||||||
<form wire:submit='saveVariables(true)' class="flex flex-col gap-2">
|
|
||||||
<x-forms.textarea rows="10" class="whitespace-pre-wrap" label="Preview Environment Variables"
|
|
||||||
id="variablesPreview"></x-forms.textarea>
|
|
||||||
<x-forms.button type="submit" class="btn btn-primary">Save</x-forms.button>
|
|
||||||
</form>
|
|
||||||
@endif
|
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
@@ -1,15 +1,6 @@
|
|||||||
<div>
|
<div>
|
||||||
<form wire:submit='submit'
|
<form wire:submit='submit'
|
||||||
class="flex flex-col items-center gap-4 p-4 bg-white border lg:items-start dark:bg-base dark:border-coolgray-300">
|
class="flex flex-col items-center gap-4 p-4 bg-white border lg:items-start dark:bg-base dark:border-coolgray-300">
|
||||||
{{-- @if (!$env->isFoundInCompose && !$isSharedVariable)
|
|
||||||
<div class="flex items-center justify-center gap-2 dark:text-warning text-coollabs"> <svg
|
|
||||||
class="hidden w-4 h-4 dark:text-warning lg:block" viewBox="0 0 256 256"
|
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path fill="currentColor"
|
|
||||||
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16">
|
|
||||||
</path>
|
|
||||||
</svg>This variable is not found in the compose file, so it won't be used.</div>
|
|
||||||
@endif --}}
|
|
||||||
@if ($isLocked)
|
@if ($isLocked)
|
||||||
<div class="flex flex-1 w-full gap-2">
|
<div class="flex flex-1 w-full gap-2">
|
||||||
<x-forms.input disabled id="env.key" />
|
<x-forms.input disabled id="env.key" />
|
||||||
|
@@ -33,7 +33,7 @@
|
|||||||
screen.scrollTop = 0;
|
screen.scrollTop = 0;
|
||||||
}
|
}
|
||||||
}">
|
}">
|
||||||
<div class="flex items-center gap-2 ">
|
<div class="flex gap-2 items-center">
|
||||||
@if ($resource?->type() === 'application' || str($resource?->type())->startsWith('standalone'))
|
@if ($resource?->type() === 'application' || str($resource?->type())->startsWith('standalone'))
|
||||||
<h4>{{ $container }}</h4>
|
<h4>{{ $container }}</h4>
|
||||||
@else
|
@else
|
||||||
@@ -46,9 +46,9 @@
|
|||||||
<x-loading wire:poll.2000ms='getLogs(true)' />
|
<x-loading wire:poll.2000ms='getLogs(true)' />
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
<form wire:submit='getLogs(true)' class="flex items-end gap-2 ">
|
<form wire:submit='getLogs(true)' class="flex gap-2 items-end">
|
||||||
<div class="w-96">
|
<div class="w-96">
|
||||||
<x-forms.input label="Only Show Number of Lines" placeholder="1000" required
|
<x-forms.input label="Only Show Number of Lines" placeholder="1000" type="number" required
|
||||||
id="numberOfLines"></x-forms.input>
|
id="numberOfLines"></x-forms.input>
|
||||||
</div>
|
</div>
|
||||||
<x-forms.button type="submit">Refresh</x-forms.button>
|
<x-forms.button type="submit">Refresh</x-forms.button>
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
<x-forms.checkbox instantSave label="Include Timestamps" id="showTimeStamps"></x-forms.checkbox>
|
<x-forms.checkbox instantSave label="Include Timestamps" id="showTimeStamps"></x-forms.checkbox>
|
||||||
</form>
|
</form>
|
||||||
<div :class="fullscreen ? 'fullscreen' : 'relative w-full py-4 mx-auto'">
|
<div :class="fullscreen ? 'fullscreen' : 'relative w-full py-4 mx-auto'">
|
||||||
<div class="flex flex-col-reverse w-full px-4 py-2 overflow-y-auto bg-white dark:text-white dark:bg-coolgray-100 scrollbar dark:border-coolgray-300"
|
<div class="flex overflow-y-auto flex-col-reverse px-4 py-2 w-full bg-white dark:text-white dark:bg-coolgray-100 scrollbar dark:border-coolgray-300"
|
||||||
:class="fullscreen ? '' : 'max-h-96 border border-solid rounded'">
|
:class="fullscreen ? '' : 'max-h-96 border border-solid rounded'">
|
||||||
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4"
|
<button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4"
|
||||||
x-on:click="makeFullscreen"><svg class="icon" viewBox="0 0 24 24"
|
x-on:click="makeFullscreen"><svg class="icon" viewBox="0 0 24 24"
|
||||||
@@ -76,7 +76,7 @@
|
|||||||
stroke-width="2" d="M12 5v14m4-4l-4 4m-4-4l4 4" />
|
stroke-width="2" d="M12 5v14m4-4l-4 4m-4-4l4 4" />
|
||||||
</svg></button>
|
</svg></button>
|
||||||
|
|
||||||
<button title="Fullscreen" x-show="!fullscreen" class="absolute top-5 right-1"
|
<button title="Fullscreen" x-show="!fullscreen" class="absolute right-1 top-5"
|
||||||
x-on:click="makeFullscreen"><svg class="icon" viewBox="0 0 24 24"
|
x-on:click="makeFullscreen"><svg class="icon" viewBox="0 0 24 24"
|
||||||
xmlns="http://www.w3.org/2000/svg">
|
xmlns="http://www.w3.org/2000/svg">
|
||||||
<g fill="none">
|
<g fill="none">
|
||||||
|