commit
3bfac7ef3b
@ -33,12 +33,13 @@ # Donations
|
||||
<a href="https://cccareers.org/" target="_blank"><img src="./other/logos/ccc-logo.webp" alt="cccareers logo" width="200"/></a>
|
||||
|
||||
## Github Sponsors ($40+)
|
||||
<a href="https://bc.direct"><img width="60px" alt="BC Direct" src="https://github.com/coollabsio/coolify/assets/5845193/a4063c41-95ed-4a32-8814-cd1475572e37"/></a>
|
||||
<a href="https://typebot.io/?utm_source=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
|
||||
<a href="https://americancloud.com/?utm_source=coolify.io"><img src="https://github.com/American-Cloud.png" width="60px" alt="American Cloud"/></a>
|
||||
<a href="https://cryptojobslist.com/?utm_source=coolify.io"><img src="https://github.com/cryptojobslist.png" width="60px" alt="CryptoJobsList" /></a>
|
||||
<a href="https://typebot.io/?utm_source=coolify.io"><img src="https://pbs.twimg.com/profile_images/1509194008366657543/9I-C7uWT_400x400.jpg" width="60px" alt="typebot"/></a>
|
||||
<a href="https://bc.direct"><img width="60px" alt="BC Direct" src="https://github.com/coollabsio/coolify/assets/5845193/a4063c41-95ed-4a32-8814-cd1475572e37"/></a>
|
||||
<a href="https://www.uxwizz.com/?utm_source=coolify.io"><img width="60px" alt="UXWizz" src="https://github.com/UXWizz.png"/></a>
|
||||
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Corentin Clichy" /></a>
|
||||
<a href="https://github.com/Flowko"><img src="https://barrad.me/_ipx/f_webp&s_300x300/younes.jpg" width="60px" alt="Younes Barrad" /></a>
|
||||
<a href="https://github.com/automazeio"><img src="https://github.com/automazeio.png" width="60px" alt="Automaze" /></a>
|
||||
<a href="https://github.com/corentinclichy"><img src="https://github.com/corentinclichy.png" width="60px" alt="Corentin Clichy" /></a>
|
||||
<a href="https://github.com/Niki2k1"><img src="https://github.com/Niki2k1.png" width="60px" alt="Niklas Lausch" /></a>
|
||||
<a href="https://github.com/pixelinfinito"><img src="https://github.com/pixelinfinito.png" width="60px" alt="Pixel Infinito" /></a>
|
||||
|
159
app/Actions/Database/StartClickhouse.php
Normal file
159
app/Actions/Database/StartClickhouse.php
Normal file
@ -0,0 +1,159 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StartClickhouse
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public StandaloneClickhouse $database;
|
||||
public array $commands = [];
|
||||
public string $configuration_dir;
|
||||
|
||||
public function handle(StandaloneClickhouse $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
|
||||
|
||||
$container_name = $this->database->uuid;
|
||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $this->database->image,
|
||||
'container_name' => $container_name,
|
||||
'environment' => $environment_variables,
|
||||
'restart' => RESTART_MODE,
|
||||
'networks' => [
|
||||
$this->database->destination->network,
|
||||
],
|
||||
'ulimits' => [
|
||||
'nofile' => [
|
||||
'soft' => 262144,
|
||||
'hard' => 262144,
|
||||
],
|
||||
],
|
||||
'labels' => [
|
||||
'coolify.managed' => 'true',
|
||||
],
|
||||
'healthcheck' => [
|
||||
'test' => "clickhouse-client --password {$this->database->clickhouse_admin_password} --query 'SELECT 1'",
|
||||
'interval' => '5s',
|
||||
'timeout' => '5s',
|
||||
'retries' => 10,
|
||||
'start_period' => '5s'
|
||||
],
|
||||
'mem_limit' => $this->database->limits_memory,
|
||||
'memswap_limit' => $this->database->limits_memory_swap,
|
||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||
'cpus' => (float) $this->database->limits_cpus,
|
||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||
]
|
||||
],
|
||||
'networks' => [
|
||||
$this->database->destination->network => [
|
||||
'external' => true,
|
||||
'name' => $this->database->destination->network,
|
||||
'attachable' => true,
|
||||
]
|
||||
]
|
||||
];
|
||||
if (!is_null($this->database->limits_cpuset)) {
|
||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||
}
|
||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||
$docker_compose['services'][$container_name]['logging'] = [
|
||||
'driver' => 'fluentd',
|
||||
'options' => [
|
||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||
'fluentd-async' => "true",
|
||||
'fluentd-sub-second-precision' => "true",
|
||||
]
|
||||
];
|
||||
}
|
||||
if (count($this->database->ports_mappings_array) > 0) {
|
||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||
}
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$docker_compose_base64 = base64_encode($docker_compose);
|
||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||
$readme = generate_readme_file($this->database->name, now());
|
||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||
$this->commands[] = "echo 'Database started.'";
|
||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||
}
|
||||
|
||||
private function generate_local_persistent_volumes()
|
||||
{
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
||||
} else {
|
||||
$volume_name = $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
}
|
||||
return $local_persistent_volumes;
|
||||
}
|
||||
|
||||
private function generate_local_persistent_volumes_only_volume_names()
|
||||
{
|
||||
$local_persistent_volumes_names = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
if ($persistentStorage->host_path) {
|
||||
continue;
|
||||
}
|
||||
$name = $persistentStorage->name;
|
||||
$local_persistent_volumes_names[$name] = [
|
||||
'name' => $name,
|
||||
'external' => false,
|
||||
];
|
||||
}
|
||||
return $local_persistent_volumes_names;
|
||||
}
|
||||
|
||||
private function generate_environment_variables()
|
||||
{
|
||||
$environment_variables = collect();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('CLICKHOUSE_ADMIN_USER'))->isEmpty()) {
|
||||
$environment_variables->push("CLICKHOUSE_ADMIN_USER={$this->database->clickhouse_admin_user}");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('CLICKHOUSE_ADMIN_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("CLICKHOUSE_ADMIN_PASSWORD={$this->database->clickhouse_admin_password}");
|
||||
}
|
||||
|
||||
return $environment_variables->all();
|
||||
}
|
||||
}
|
@ -3,6 +3,9 @@
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
@ -15,7 +18,7 @@ class StartDatabaseProxy
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|ServiceDatabase $database)
|
||||
{
|
||||
$internalPort = null;
|
||||
$type = $database->getMorphClass();
|
||||
@ -50,6 +53,18 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
|
||||
$type = 'App\Models\StandaloneRedis';
|
||||
$containerName = "redis-{$database->service->uuid}";
|
||||
break;
|
||||
case 'standalone-keydb':
|
||||
$type = 'App\Models\StandaloneKeydb';
|
||||
$containerName = "keydb-{$database->service->uuid}";
|
||||
break;
|
||||
case 'standalone-dragonfly':
|
||||
$type = 'App\Models\StandaloneDragonfly';
|
||||
$containerName = "dragonfly-{$database->service->uuid}";
|
||||
break;
|
||||
case 'standalone-clickhouse':
|
||||
$type = 'App\Models\StandaloneClickhouse';
|
||||
$containerName = "clickhouse-{$database->service->uuid}";
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($type === 'App\Models\StandaloneRedis') {
|
||||
@ -62,6 +77,12 @@ public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|St
|
||||
$internalPort = 3306;
|
||||
} else if ($type === 'App\Models\StandaloneMariadb') {
|
||||
$internalPort = 3306;
|
||||
} else if ($type === 'App\Models\StandaloneKeydb') {
|
||||
$internalPort = 6379;
|
||||
} else if ($type === 'App\Models\StandaloneDragonfly') {
|
||||
$internalPort = 6379;
|
||||
} else if ($type === 'App\Models\StandaloneClickhouse') {
|
||||
$internalPort = 9000;
|
||||
}
|
||||
$configuration_dir = database_proxy_dir($database->uuid);
|
||||
$nginxconf = <<<EOF
|
||||
|
156
app/Actions/Database/StartDragonfly.php
Normal file
156
app/Actions/Database/StartDragonfly.php
Normal file
@ -0,0 +1,156 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StartDragonfly
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public StandaloneDragonfly $database;
|
||||
public array $commands = [];
|
||||
public string $configuration_dir;
|
||||
|
||||
|
||||
public function handle(StandaloneDragonfly $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
|
||||
$startCommand = "dragonfly --requirepass {$this->database->dragonfly_password}";
|
||||
|
||||
$container_name = $this->database->uuid;
|
||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $this->database->image,
|
||||
'command' => $startCommand,
|
||||
'container_name' => $container_name,
|
||||
'environment' => $environment_variables,
|
||||
'restart' => RESTART_MODE,
|
||||
'networks' => [
|
||||
$this->database->destination->network,
|
||||
],
|
||||
'ulimits' => [
|
||||
'memlock'=> '-1'
|
||||
],
|
||||
'labels' => [
|
||||
'coolify.managed' => 'true',
|
||||
],
|
||||
'healthcheck' => [
|
||||
'test' => "redis-cli -a {$this->database->dragonfly_password} ping",
|
||||
'interval' => '5s',
|
||||
'timeout' => '5s',
|
||||
'retries' => 10,
|
||||
'start_period' => '5s'
|
||||
],
|
||||
'mem_limit' => $this->database->limits_memory,
|
||||
'memswap_limit' => $this->database->limits_memory_swap,
|
||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||
'cpus' => (float) $this->database->limits_cpus,
|
||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||
]
|
||||
],
|
||||
'networks' => [
|
||||
$this->database->destination->network => [
|
||||
'external' => true,
|
||||
'name' => $this->database->destination->network,
|
||||
'attachable' => true,
|
||||
]
|
||||
]
|
||||
];
|
||||
if (!is_null($this->database->limits_cpuset)) {
|
||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||
}
|
||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||
$docker_compose['services'][$container_name]['logging'] = [
|
||||
'driver' => 'fluentd',
|
||||
'options' => [
|
||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||
'fluentd-async' => "true",
|
||||
'fluentd-sub-second-precision' => "true",
|
||||
]
|
||||
];
|
||||
}
|
||||
if (count($this->database->ports_mappings_array) > 0) {
|
||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||
}
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$docker_compose_base64 = base64_encode($docker_compose);
|
||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||
$readme = generate_readme_file($this->database->name, now());
|
||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||
$this->commands[] = "echo 'Database started.'";
|
||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||
}
|
||||
|
||||
private function generate_local_persistent_volumes()
|
||||
{
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
||||
} else {
|
||||
$volume_name = $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
}
|
||||
return $local_persistent_volumes;
|
||||
}
|
||||
|
||||
private function generate_local_persistent_volumes_only_volume_names()
|
||||
{
|
||||
$local_persistent_volumes_names = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
if ($persistentStorage->host_path) {
|
||||
continue;
|
||||
}
|
||||
$name = $persistentStorage->name;
|
||||
$local_persistent_volumes_names[$name] = [
|
||||
'name' => $name,
|
||||
'external' => false,
|
||||
];
|
||||
}
|
||||
return $local_persistent_volumes_names;
|
||||
}
|
||||
|
||||
private function generate_environment_variables()
|
||||
{
|
||||
$environment_variables = collect();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("REDIS_PASSWORD={$this->database->dragonfly_password}");
|
||||
}
|
||||
|
||||
return $environment_variables->all();
|
||||
}
|
||||
}
|
174
app/Actions/Database/StartKeydb.php
Normal file
174
app/Actions/Database/StartKeydb.php
Normal file
@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\StandaloneKeydb;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StartKeydb
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public StandaloneKeydb $database;
|
||||
public array $commands = [];
|
||||
public string $configuration_dir;
|
||||
|
||||
|
||||
public function handle(StandaloneKeydb $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
|
||||
$startCommand = "keydb-server --requirepass {$this->database->keydb_password} --appendonly yes";
|
||||
|
||||
$container_name = $this->database->uuid;
|
||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||
|
||||
$this->commands = [
|
||||
"echo 'Starting {$database->name}.'",
|
||||
"mkdir -p $this->configuration_dir",
|
||||
];
|
||||
|
||||
$persistent_storages = $this->generate_local_persistent_volumes();
|
||||
$volume_names = $this->generate_local_persistent_volumes_only_volume_names();
|
||||
$environment_variables = $this->generate_environment_variables();
|
||||
$this->add_custom_keydb();
|
||||
|
||||
$docker_compose = [
|
||||
'version' => '3.8',
|
||||
'services' => [
|
||||
$container_name => [
|
||||
'image' => $this->database->image,
|
||||
'command' => $startCommand,
|
||||
'container_name' => $container_name,
|
||||
'environment' => $environment_variables,
|
||||
'restart' => RESTART_MODE,
|
||||
'networks' => [
|
||||
$this->database->destination->network,
|
||||
],
|
||||
'labels' => [
|
||||
'coolify.managed' => 'true',
|
||||
],
|
||||
'healthcheck' => [
|
||||
'test' => "keydb-cli --pass {$this->database->keydb_password} ping",
|
||||
'interval' => '5s',
|
||||
'timeout' => '5s',
|
||||
'retries' => 10,
|
||||
'start_period' => '5s'
|
||||
],
|
||||
'mem_limit' => $this->database->limits_memory,
|
||||
'memswap_limit' => $this->database->limits_memory_swap,
|
||||
'mem_swappiness' => $this->database->limits_memory_swappiness,
|
||||
'mem_reservation' => $this->database->limits_memory_reservation,
|
||||
'cpus' => (float) $this->database->limits_cpus,
|
||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||
]
|
||||
],
|
||||
'networks' => [
|
||||
$this->database->destination->network => [
|
||||
'external' => true,
|
||||
'name' => $this->database->destination->network,
|
||||
'attachable' => true,
|
||||
]
|
||||
]
|
||||
];
|
||||
if (!is_null($this->database->limits_cpuset)) {
|
||||
data_set($docker_compose, "services.{$container_name}.cpuset", $this->database->limits_cpuset);
|
||||
}
|
||||
if ($this->database->destination->server->isLogDrainEnabled() && $this->database->isLogDrainEnabled()) {
|
||||
$docker_compose['services'][$container_name]['logging'] = [
|
||||
'driver' => 'fluentd',
|
||||
'options' => [
|
||||
'fluentd-address' => "tcp://127.0.0.1:24224",
|
||||
'fluentd-async' => "true",
|
||||
'fluentd-sub-second-precision' => "true",
|
||||
]
|
||||
];
|
||||
}
|
||||
if (count($this->database->ports_mappings_array) > 0) {
|
||||
$docker_compose['services'][$container_name]['ports'] = $this->database->ports_mappings_array;
|
||||
}
|
||||
if (count($persistent_storages) > 0) {
|
||||
$docker_compose['services'][$container_name]['volumes'] = $persistent_storages;
|
||||
}
|
||||
if (count($volume_names) > 0) {
|
||||
$docker_compose['volumes'] = $volume_names;
|
||||
}
|
||||
if (!is_null($this->database->keydb_conf)) {
|
||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir . '/keydb.conf',
|
||||
'target' => '/etc/keydb/keydb.conf',
|
||||
'read_only' => true,
|
||||
];
|
||||
$docker_compose['services'][$container_name]['command'] = "keydb-server /etc/keydb/keydb.conf --requirepass {$this->database->keydb_password} --appendonly yes";
|
||||
}
|
||||
$docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$docker_compose_base64 = base64_encode($docker_compose);
|
||||
$this->commands[] = "echo '{$docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml";
|
||||
$readme = generate_readme_file($this->database->name, now());
|
||||
$this->commands[] = "echo '{$readme}' > $this->configuration_dir/README.md";
|
||||
$this->commands[] = "echo 'Pulling {$database->image} image.'";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml pull";
|
||||
$this->commands[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||
$this->commands[] = "echo 'Database started.'";
|
||||
return remote_process($this->commands, $database->destination->server, callEventOnFinish: 'DatabaseStatusChanged');
|
||||
}
|
||||
|
||||
private function generate_local_persistent_volumes()
|
||||
{
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
||||
} else {
|
||||
$volume_name = $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
}
|
||||
return $local_persistent_volumes;
|
||||
}
|
||||
|
||||
private function generate_local_persistent_volumes_only_volume_names()
|
||||
{
|
||||
$local_persistent_volumes_names = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
if ($persistentStorage->host_path) {
|
||||
continue;
|
||||
}
|
||||
$name = $persistentStorage->name;
|
||||
$local_persistent_volumes_names[$name] = [
|
||||
'name' => $name,
|
||||
'external' => false,
|
||||
];
|
||||
}
|
||||
return $local_persistent_volumes_names;
|
||||
}
|
||||
|
||||
private function generate_environment_variables()
|
||||
{
|
||||
$environment_variables = collect();
|
||||
foreach ($this->database->runtime_environment_variables as $env) {
|
||||
$environment_variables->push("$env->key=$env->real_value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("REDIS_PASSWORD={$this->database->keydb_password}");
|
||||
}
|
||||
|
||||
return $environment_variables->all();
|
||||
}
|
||||
private function add_custom_keydb()
|
||||
{
|
||||
if (is_null($this->database->keydb_conf)) {
|
||||
return;
|
||||
}
|
||||
$filename = 'keydb.conf';
|
||||
Storage::disk('local')->put("tmp/keydb.conf_{$this->database->uuid}", $this->database->keydb_conf);
|
||||
$path = Storage::path("tmp/keydb.conf_{$this->database->uuid}");
|
||||
instant_scp($path, "{$this->configuration_dir}/{$filename}", $this->database->destination->server);
|
||||
Storage::disk('local')->delete("tmp/keydb.conf_{$this->database->uuid}");
|
||||
}
|
||||
}
|
@ -114,8 +114,12 @@ private function generate_local_persistent_volumes()
|
||||
{
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
||||
} else {
|
||||
$volume_name = $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
}
|
||||
return $local_persistent_volumes;
|
||||
}
|
||||
|
@ -130,8 +130,12 @@ private function generate_local_persistent_volumes()
|
||||
{
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
||||
} else {
|
||||
$volume_name = $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
}
|
||||
return $local_persistent_volumes;
|
||||
}
|
||||
|
@ -114,8 +114,12 @@ private function generate_local_persistent_volumes()
|
||||
{
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
||||
} else {
|
||||
$volume_name = $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
}
|
||||
return $local_persistent_volumes;
|
||||
}
|
||||
|
@ -136,8 +136,12 @@ private function generate_local_persistent_volumes()
|
||||
{
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
||||
} else {
|
||||
$volume_name = $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
}
|
||||
return $local_persistent_volumes;
|
||||
}
|
||||
|
@ -125,8 +125,12 @@ private function generate_local_persistent_volumes()
|
||||
{
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->database->persistentStorages as $persistentStorage) {
|
||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||
$local_persistent_volumes[] = $persistentStorage->host_path . ':' . $persistentStorage->mount_path;
|
||||
} else {
|
||||
$volume_name = $persistentStorage->name;
|
||||
$local_persistent_volumes[] = $volume_name . ':' . $persistentStorage->mount_path;
|
||||
}
|
||||
}
|
||||
return $local_persistent_volumes;
|
||||
}
|
||||
|
@ -2,7 +2,9 @@
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Events\DatabaseStatusChanged;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
@ -14,7 +16,7 @@ class StopDatabase
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $database)
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||
{
|
||||
$server = $database->destination->server;
|
||||
if (!$server->isFunctional()) {
|
||||
|
@ -3,6 +3,9 @@
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
@ -14,7 +17,7 @@ class StopDatabaseProxy
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|ServiceDatabase $database)
|
||||
public function handle(StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|ServiceDatabase|StandaloneDragonfly|StandaloneClickhouse $database)
|
||||
{
|
||||
$server = data_get($database, 'destination.server');
|
||||
$uuid = $database->uuid;
|
||||
|
@ -7,6 +7,9 @@
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
@ -55,6 +58,33 @@ private function cleanup_stucked_resources()
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck redis: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$keydbs = StandaloneKeydb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($keydbs as $keydb) {
|
||||
echo "Deleting stuck keydb: {$keydb->name}\n";
|
||||
$redis->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck keydb: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$dragonflies = StandaloneDragonfly::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($dragonflies as $dragonfly) {
|
||||
echo "Deleting stuck dragonfly: {$dragonfly->name}\n";
|
||||
$redis->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck dragonfly: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$clickhouses = StandaloneClickhouse::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($clickhouses as $clickhouse) {
|
||||
echo "Deleting stuck clickhouse: {$clickhouse->name}\n";
|
||||
$redis->forceDelete();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
echo "Error in cleaning stuck clickhouse: {$e->getMessage()}\n";
|
||||
}
|
||||
try {
|
||||
$mongodbs = StandaloneMongodb::withTrashed()->whereNotNull('deleted_at')->get();
|
||||
foreach ($mongodbs as $mongodb) {
|
||||
|
@ -33,6 +33,7 @@ protected function schedule(Schedule $schedule): void
|
||||
$this->check_scheduled_backups($schedule);
|
||||
$this->pull_helper_image($schedule);
|
||||
$this->check_scheduled_tasks($schedule);
|
||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||
} else {
|
||||
// Instance Jobs
|
||||
$schedule->command('horizon:snapshot')->everyFiveMinutes();
|
||||
@ -49,6 +50,7 @@ protected function schedule(Schedule $schedule): void
|
||||
$this->check_scheduled_tasks($schedule);
|
||||
|
||||
$schedule->command('cleanup:database --yes')->daily();
|
||||
$schedule->command('uploads:clear')->everyTwoMinutes();
|
||||
}
|
||||
}
|
||||
private function pull_helper_image($schedule)
|
||||
|
@ -2,6 +2,9 @@
|
||||
|
||||
namespace App\Http\Controllers\Api;
|
||||
|
||||
use App\Actions\Database\StartClickhouse;
|
||||
use App\Actions\Database\StartDragonfly;
|
||||
use App\Actions\Database\StartKeydb;
|
||||
use App\Actions\Database\StartMariadb;
|
||||
use App\Actions\Database\StartMongodb;
|
||||
use App\Actions\Database\StartMysql;
|
||||
@ -157,6 +160,24 @@ public function deploy_resource($resource, bool $force = false): array
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message = "Database {$resource->name} started.";
|
||||
} else if ($type === 'App\Models\StandaloneKeydb') {
|
||||
StartKeydb::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message = "Database {$resource->name} started.";
|
||||
} else if ($type === 'App\Models\StandaloneDragonfly') {
|
||||
StartDragonfly::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message = "Database {$resource->name} started.";
|
||||
} else if ($type === 'App\Models\StandaloneClickhouse') {
|
||||
StartClickhouse::run($resource);
|
||||
$resource->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$message = "Database {$resource->name} started.";
|
||||
} else if ($type === 'App\Models\StandaloneMongodb') {
|
||||
StartMongodb::run($resource);
|
||||
$resource->update([
|
||||
|
83
app/Http/Controllers/UploadController.php
Normal file
83
app/Http/Controllers/UploadController.php
Normal file
@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use Illuminate\Routing\Controller as BaseController;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Pion\Laravel\ChunkUpload\Exceptions\UploadFailedException;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Pion\Laravel\ChunkUpload\Exceptions\UploadMissingFileException;
|
||||
use Pion\Laravel\ChunkUpload\Handler\AbstractHandler;
|
||||
use Pion\Laravel\ChunkUpload\Handler\HandlerFactory;
|
||||
use Pion\Laravel\ChunkUpload\Receiver\FileReceiver;
|
||||
|
||||
class UploadController extends BaseController
|
||||
{
|
||||
public function upload(Request $request)
|
||||
{
|
||||
$resource = getResourceByUuid(request()->route('databaseUuid'), data_get(auth()->user()->currentTeam(), 'id'));
|
||||
if (is_null($resource)) {
|
||||
return response()->json(['error' => 'You do not have permission for this database'], 500);
|
||||
}
|
||||
$receiver = new FileReceiver("file", $request, HandlerFactory::classFromRequest($request));
|
||||
|
||||
if ($receiver->isUploaded() === false) {
|
||||
throw new UploadMissingFileException();
|
||||
}
|
||||
|
||||
$save = $receiver->receive();
|
||||
|
||||
if ($save->isFinished()) {
|
||||
return $this->saveFile($save->getFile(), $resource);
|
||||
}
|
||||
|
||||
$handler = $save->handler();
|
||||
return response()->json([
|
||||
"done" => $handler->getPercentageDone(),
|
||||
'status' => true
|
||||
]);
|
||||
}
|
||||
// protected function saveFileToS3($file)
|
||||
// {
|
||||
// $fileName = $this->createFilename($file);
|
||||
|
||||
// $disk = Storage::disk('s3');
|
||||
// // It's better to use streaming Streaming (laravel 5.4+)
|
||||
// $disk->putFileAs('photos', $file, $fileName);
|
||||
|
||||
// // for older laravel
|
||||
// // $disk->put($fileName, file_get_contents($file), 'public');
|
||||
// $mime = str_replace('/', '-', $file->getMimeType());
|
||||
|
||||
// // We need to delete the file when uploaded to s3
|
||||
// unlink($file->getPathname());
|
||||
|
||||
// return response()->json([
|
||||
// 'path' => $disk->url($fileName),
|
||||
// 'name' => $fileName,
|
||||
// 'mime_type' => $mime
|
||||
// ]);
|
||||
// }
|
||||
protected function saveFile(UploadedFile $file, $resource)
|
||||
{
|
||||
$mime = str_replace('/', '-', $file->getMimeType());
|
||||
$filePath = "upload/{$resource->uuid}";
|
||||
$finalPath = storage_path("app/" . $filePath);
|
||||
$file->move($finalPath, 'restore');
|
||||
|
||||
return response()->json([
|
||||
'mime_type' => $mime
|
||||
]);
|
||||
}
|
||||
protected function createFilename(UploadedFile $file)
|
||||
{
|
||||
$extension = $file->getClientOriginalExtension();
|
||||
$filename = str_replace("." . $extension, "", $file->getClientOriginalName()); // Filename without extension
|
||||
|
||||
$filename .= "_" . md5(time()) . "." . $extension;
|
||||
|
||||
return $filename;
|
||||
}
|
||||
}
|
@ -83,6 +83,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
private $env_nixpacks_args;
|
||||
private $docker_compose;
|
||||
private $docker_compose_base64;
|
||||
private ?string $env_filename = null;
|
||||
private ?string $nixpacks_plan = null;
|
||||
private ?string $nixpacks_type = null;
|
||||
private string $dockerfile_location = '/Dockerfile';
|
||||
@ -239,50 +240,7 @@ public function handle(): void
|
||||
$this->build_server = $this->server;
|
||||
$this->original_server = $this->server;
|
||||
}
|
||||
if ($this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile') {
|
||||
$this->just_restart();
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
dispatch(new ContainerStatusJob($this->server));
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
$this->application->isConfigurationChanged(false);
|
||||
$this->run_post_deployment_command();
|
||||
return;
|
||||
} else if ($this->pull_request_id !== 0) {
|
||||
$this->deploy_pull_request();
|
||||
} else if ($this->application->dockerfile) {
|
||||
$this->deploy_simple_dockerfile();
|
||||
} else if ($this->application->build_pack === 'dockercompose') {
|
||||
$this->deploy_docker_compose_buildpack();
|
||||
} else if ($this->application->build_pack === 'dockerimage') {
|
||||
$this->deploy_dockerimage_buildpack();
|
||||
} else if ($this->application->build_pack === 'dockerfile') {
|
||||
$this->deploy_dockerfile_buildpack();
|
||||
} else if ($this->application->build_pack === 'static') {
|
||||
$this->deploy_static_buildpack();
|
||||
} else {
|
||||
$this->deploy_nixpacks_buildpack();
|
||||
}
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
dispatch(new ContainerStatusJob($this->server));
|
||||
}
|
||||
// Otherwise built image needs to be pushed before from the build server.
|
||||
// ray($this->use_build_server);
|
||||
// if (!$this->use_build_server) {
|
||||
// if ($this->application->additional_servers->count() > 0) {
|
||||
// $this->push_to_docker_registry(forceFail: true);
|
||||
// } else {
|
||||
// $this->push_to_docker_registry();
|
||||
// }
|
||||
// }
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
if ($this->pull_request_id !== 0) {
|
||||
if ($this->application->is_github_based()) {
|
||||
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::FINISHED);
|
||||
}
|
||||
}
|
||||
$this->run_post_deployment_command();
|
||||
$this->application->isConfigurationChanged(true);
|
||||
$this->decide_what_to_do();
|
||||
} catch (Exception $e) {
|
||||
if ($this->pull_request_id !== 0 && $this->application->is_github_based()) {
|
||||
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::ERROR);
|
||||
@ -317,6 +275,43 @@ public function handle(): void
|
||||
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
|
||||
}
|
||||
}
|
||||
private function decide_what_to_do()
|
||||
{
|
||||
if ($this->restart_only && $this->application->build_pack !== 'dockerimage' && $this->application->build_pack !== 'dockerfile') {
|
||||
$this->just_restart();
|
||||
return;
|
||||
} else if ($this->pull_request_id !== 0) {
|
||||
$this->deploy_pull_request();
|
||||
} else if ($this->application->dockerfile) {
|
||||
$this->deploy_simple_dockerfile();
|
||||
} else if ($this->application->build_pack === 'dockercompose') {
|
||||
$this->deploy_docker_compose_buildpack();
|
||||
} else if ($this->application->build_pack === 'dockerimage') {
|
||||
$this->deploy_dockerimage_buildpack();
|
||||
} else if ($this->application->build_pack === 'dockerfile') {
|
||||
$this->deploy_dockerfile_buildpack();
|
||||
} else if ($this->application->build_pack === 'static') {
|
||||
$this->deploy_static_buildpack();
|
||||
} else {
|
||||
$this->deploy_nixpacks_buildpack();
|
||||
}
|
||||
$this->post_deployment();
|
||||
}
|
||||
private function post_deployment()
|
||||
{
|
||||
|
||||
if ($this->server->isProxyShouldRun()) {
|
||||
dispatch(new ContainerStatusJob($this->server));
|
||||
}
|
||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||
if ($this->pull_request_id !== 0) {
|
||||
if ($this->application->is_github_based()) {
|
||||
ApplicationPullRequestUpdateJob::dispatch(application: $this->application, preview: $this->preview, deployment_uuid: $this->deployment_uuid, status: ProcessStatus::FINISHED);
|
||||
}
|
||||
}
|
||||
$this->run_post_deployment_command();
|
||||
$this->application->isConfigurationChanged(true);
|
||||
}
|
||||
private function deploy_simple_dockerfile()
|
||||
{
|
||||
if ($this->use_build_server) {
|
||||
@ -336,7 +331,6 @@ private function deploy_simple_dockerfile()
|
||||
// if (!$this->force_rebuild) {
|
||||
// $this->check_image_locally_or_remotely();
|
||||
// if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||
// $this->create_workdir();
|
||||
// $this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||
// $this->generate_compose_file();
|
||||
// $this->push_to_docker_registry();
|
||||
@ -477,7 +471,6 @@ private function deploy_dockerfile_buildpack()
|
||||
if (!$this->force_rebuild) {
|
||||
$this->check_image_locally_or_remotely();
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||
$this->create_workdir();
|
||||
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||
$this->generate_compose_file();
|
||||
$this->push_to_docker_registry();
|
||||
@ -506,7 +499,6 @@ private function deploy_nixpacks_buildpack()
|
||||
if (!$this->force_rebuild) {
|
||||
$this->check_image_locally_or_remotely();
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||
$this->create_workdir();
|
||||
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||
$this->generate_compose_file();
|
||||
ray('pushing to docker registry');
|
||||
@ -540,7 +532,6 @@ private function deploy_static_buildpack()
|
||||
if (!$this->force_rebuild) {
|
||||
$this->check_image_locally_or_remotely();
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||
$this->create_workdir();
|
||||
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
|
||||
$this->generate_compose_file();
|
||||
$this->push_to_docker_registry();
|
||||
@ -683,7 +674,7 @@ private function generate_image_names()
|
||||
}
|
||||
private function just_restart()
|
||||
{
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
|
||||
$this->application_deployment_queue->addLogEntry("Restarting {$this->customRepository}:{$this->application->git_branch} on {$this->server->name}.");
|
||||
$this->prepare_builder_image();
|
||||
$this->check_git_if_build_needed();
|
||||
$this->set_base_dir();
|
||||
@ -691,12 +682,14 @@ private function just_restart()
|
||||
$this->check_image_locally_or_remotely();
|
||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
||||
$this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Restarting container.");
|
||||
$this->create_workdir();
|
||||
$this->generate_compose_file();
|
||||
$this->rolling_update();
|
||||
return;
|
||||
$this->post_deployment();
|
||||
} else {
|
||||
$this->application_deployment_queue->addLogEntry("Image not found ({$this->production_image_name}). Redeploying the application.");
|
||||
$this->restart_only = false;
|
||||
$this->decide_what_to_do();
|
||||
}
|
||||
throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.');
|
||||
}
|
||||
private function check_image_locally_or_remotely()
|
||||
{
|
||||
@ -716,19 +709,35 @@ private function save_environment_variables()
|
||||
{
|
||||
$envs = collect([]);
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$this->env_filename = ".env-pr-$this->pull_request_id";
|
||||
foreach ($this->application->environment_variables_preview as $env) {
|
||||
$envs->push($env->key . '=' . $env->real_value);
|
||||
}
|
||||
} else {
|
||||
$this->env_filename = ".env";
|
||||
foreach ($this->application->environment_variables as $env) {
|
||||
$envs->push($env->key . '=' . $env->real_value);
|
||||
}
|
||||
}
|
||||
if ($envs->isEmpty()) {
|
||||
$this->env_filename = null;
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"command" => "rm -f $this->configuration_dir/{$this->env_filename}",
|
||||
"hidden" => true,
|
||||
"ignore_errors" => true
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
$envs_base64 = base64_encode($envs->implode("\n"));
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env")
|
||||
],
|
||||
[
|
||||
"echo '$envs_base64' | base64 -d > $this->configuration_dir/{$this->env_filename}"
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@ -868,6 +877,9 @@ private function create_workdir()
|
||||
[
|
||||
"command" => executeInDocker($this->deployment_uuid, "mkdir -p {$this->workdir}")
|
||||
],
|
||||
[
|
||||
"command" => "mkdir -p {$this->configuration_dir}"
|
||||
],
|
||||
);
|
||||
}
|
||||
private function prepare_builder_image()
|
||||
@ -890,6 +902,11 @@ private function prepare_builder_image()
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Preparing container with helper image: $helperImage.");
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"command" => "docker rm -f {$this->deployment_uuid}",
|
||||
"ignore_errors" => true,
|
||||
"hidden" => true
|
||||
],
|
||||
[
|
||||
$runCommand,
|
||||
"hidden" => true,
|
||||
@ -993,6 +1010,7 @@ private function clone_repository()
|
||||
$importCommands, "hidden" => true
|
||||
]
|
||||
);
|
||||
$this->create_workdir();
|
||||
}
|
||||
|
||||
private function generate_git_import_commands()
|
||||
@ -1098,6 +1116,7 @@ private function generate_env_variables()
|
||||
|
||||
private function generate_compose_file()
|
||||
{
|
||||
$this->create_workdir();
|
||||
$ports = $this->application->settings->is_static ? [80] : $this->application->ports_exposes_array;
|
||||
$onlyPort = null;
|
||||
if (count($ports) > 0) {
|
||||
@ -1181,6 +1200,11 @@ private function generate_compose_file()
|
||||
]
|
||||
]
|
||||
];
|
||||
if ($this->env_filename) {
|
||||
$docker_compose['services'][$this->container_name]['env_file'] = [
|
||||
$this->env_filename
|
||||
];
|
||||
}
|
||||
if (!$this->custom_healthcheck_found) {
|
||||
$docker_compose['services'][$this->container_name]['healthcheck'] = [
|
||||
'test' => [
|
||||
@ -1334,13 +1358,18 @@ private function generate_compose_file()
|
||||
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
||||
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
|
||||
$this->save_environment_variables();
|
||||
}
|
||||
|
||||
private function generate_local_persistent_volumes()
|
||||
{
|
||||
$local_persistent_volumes = [];
|
||||
foreach ($this->application->persistentStorages as $persistentStorage) {
|
||||
$volume_name = $persistentStorage->host_path ?? $persistentStorage->name;
|
||||
if ($persistentStorage->host_path !== '' && $persistentStorage->host_path !== null) {
|
||||
$volume_name = $persistentStorage->host_path;
|
||||
} else {
|
||||
$volume_name = $persistentStorage->name;
|
||||
}
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$volume_name = $volume_name . '-pr-' . $this->pull_request_id;
|
||||
}
|
||||
|
@ -8,6 +8,9 @@
|
||||
use App\Actions\Service\StopService;
|
||||
use App\Models\Application;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
@ -25,7 +28,7 @@ class DeleteResourceJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource)
|
||||
public function __construct(public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource, public bool $deleteConfigurations = false)
|
||||
{
|
||||
}
|
||||
|
||||
@ -42,6 +45,9 @@ public function handle()
|
||||
case 'standalone-mongodb':
|
||||
case 'standalone-mysql':
|
||||
case 'standalone-mariadb':
|
||||
case 'standalone-keydb':
|
||||
case 'standalone-dragonfly':
|
||||
case 'standalone-clickhouse':
|
||||
StopDatabase::run($this->resource);
|
||||
break;
|
||||
case 'service':
|
||||
@ -49,6 +55,9 @@ public function handle()
|
||||
DeleteService::run($this->resource);
|
||||
break;
|
||||
}
|
||||
if ($this->deleteConfigurations) {
|
||||
$this->resource?->delete_configurations();
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
send_internal_notification('ContainerStoppingJob failed with: ' . $e->getMessage());
|
||||
|
@ -24,7 +24,7 @@ public function mount()
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.force-password-reset')->layout('layouts.simple');
|
||||
return view('livewire.force-password-reset');
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
|
@ -24,7 +24,7 @@ class General extends Component
|
||||
|
||||
public $customLabels;
|
||||
public bool $labelsChanged = false;
|
||||
public bool $isConfigurationChanged = false;
|
||||
public bool $initLoadingCompose = false;
|
||||
|
||||
public ?string $initialDockerComposeLocation = null;
|
||||
public ?string $initialDockerComposePrLocation = null;
|
||||
@ -123,19 +123,22 @@ public function mount()
|
||||
$this->application->settings->save();
|
||||
}
|
||||
$this->parsedServiceDomains = $this->application->docker_compose_domains ? json_decode($this->application->docker_compose_domains, true) : [];
|
||||
|
||||
$this->ports_exposes = $this->application->ports_exposes;
|
||||
if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
|
||||
$this->application->isConfigurationChanged(true);
|
||||
}
|
||||
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
||||
$this->customLabels = $this->application->parseContainerLabels();
|
||||
$this->customLabels = $this->application->parseContainerLabels();
|
||||
if (!$this->customLabels && $this->application->destination->server->proxyType() !== 'NONE') {
|
||||
$this->customLabels = str(implode("|", generateLabelsApplication($this->application)))->replace("|", "\n");
|
||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||
$this->application->save();
|
||||
}
|
||||
$this->initialDockerComposeLocation = $this->application->docker_compose_location;
|
||||
if ($this->application->build_pack === 'dockercompose' && !$this->application->docker_compose_raw) {
|
||||
$this->initLoadingCompose = true;
|
||||
$this->dispatch('info', 'Loading docker compose file...');
|
||||
}
|
||||
|
||||
if (str($this->application->status)->startsWith('running') && is_null($this->application->config_hash)) {
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
@ -154,11 +157,15 @@ public function loadComposeFile($isInit = false)
|
||||
}
|
||||
['parsedServices' => $this->parsedServices, 'initialDockerComposeLocation' => $this->initialDockerComposeLocation, 'initialDockerComposePrLocation' => $this->initialDockerComposePrLocation] = $this->application->loadComposeFile($isInit);
|
||||
$this->dispatch('success', 'Docker compose file loaded.');
|
||||
$this->dispatch('compose_loaded');
|
||||
} catch (\Throwable $e) {
|
||||
$this->application->docker_compose_location = $this->initialDockerComposeLocation;
|
||||
$this->application->docker_compose_pr_location = $this->initialDockerComposePrLocation;
|
||||
$this->application->save();
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->initLoadingCompose = false;
|
||||
|
||||
}
|
||||
}
|
||||
public function generateDomain(string $serviceName)
|
||||
@ -307,7 +314,7 @@ public function submit($showToaster = true)
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->isConfigurationChanged = $this->application->isConfigurationChanged();
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ public function getListeners()
|
||||
$teamId = auth()->user()->currentTeam()->id;
|
||||
return [
|
||||
"echo-private:team.{$teamId},ApplicationStatusChanged" => 'check_status',
|
||||
"compose_loaded" => '$refresh',
|
||||
];
|
||||
}
|
||||
public function mount()
|
||||
@ -38,6 +39,7 @@ public function check_status($showNotification = false)
|
||||
}
|
||||
|
||||
if ($showNotification) $this->dispatch('success', "Success", "Application status updated.");
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
|
||||
public function force_deploy_without_cache()
|
||||
|
@ -8,7 +8,8 @@ class Index extends Component
|
||||
{
|
||||
public $database;
|
||||
public $s3s;
|
||||
public function mount() {
|
||||
public function mount()
|
||||
{
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
return redirect()->route('dashboard');
|
||||
@ -21,8 +22,13 @@ public function mount() {
|
||||
if (!$database) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
// No backups for redis
|
||||
if ($database->getMorphClass() === 'App\Models\StandaloneRedis') {
|
||||
// No backups
|
||||
if (
|
||||
$database->getMorphClass() === 'App\Models\StandaloneRedis' ||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneKeydb' ||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneDragonfly'||
|
||||
$database->getMorphClass() === 'App\Models\StandaloneClickhouse'
|
||||
) {
|
||||
return redirect()->route('project.database.configuration', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Livewire\Component;
|
||||
use Symfony\Component\HttpFoundation\StreamedResponse;
|
||||
|
||||
class BackupExecutions extends Component
|
||||
{
|
||||
@ -36,32 +37,9 @@ public function deleteBackup($exeuctionId)
|
||||
$this->dispatch('success', 'Backup deleted.');
|
||||
$this->refreshBackupExecutions();
|
||||
}
|
||||
public function download($exeuctionId)
|
||||
public function download_file($exeuctionId)
|
||||
{
|
||||
try {
|
||||
$execution = $this->backup->executions()->where('id', $exeuctionId)->first();
|
||||
if (is_null($execution)) {
|
||||
$this->dispatch('error', 'Backup execution not found.');
|
||||
return;
|
||||
}
|
||||
$filename = data_get($execution, 'filename');
|
||||
if ($execution->scheduledDatabaseBackup->database->getMorphClass() === 'App\Models\ServiceDatabase') {
|
||||
$server = $execution->scheduledDatabaseBackup->database->service->destination->server;
|
||||
} else {
|
||||
$server = $execution->scheduledDatabaseBackup->database->destination->server;
|
||||
}
|
||||
$privateKeyLocation = savePrivateKeyToFs($server);
|
||||
$disk = Storage::build([
|
||||
'driver' => 'sftp',
|
||||
'host' => $server->ip,
|
||||
'port' => $server->port,
|
||||
'username' => $server->user,
|
||||
'privateKey' => $privateKeyLocation,
|
||||
]);
|
||||
return $disk->download($filename);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
return redirect()->route('download.backup', $exeuctionId);
|
||||
}
|
||||
public function refreshBackupExecutions(): void
|
||||
{
|
||||
|
115
app/Livewire/Project/Database/Clickhouse/General.php
Normal file
115
app/Livewire/Project/Database/Clickhouse/General.php
Normal file
@ -0,0 +1,115 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Database\Clickhouse;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
|
||||
class General extends Component
|
||||
{
|
||||
public StandaloneClickhouse $database;
|
||||
public ?string $db_url = null;
|
||||
public ?string $db_url_public = null;
|
||||
|
||||
protected $listeners = ['refresh'];
|
||||
|
||||
protected $rules = [
|
||||
'database.name' => 'required',
|
||||
'database.description' => 'nullable',
|
||||
'database.clickhouse_admin_user' => 'required',
|
||||
'database.clickhouse_admin_password' => 'required',
|
||||
'database.image' => 'required',
|
||||
'database.ports_mappings' => 'nullable',
|
||||
'database.is_public' => 'nullable|boolean',
|
||||
'database.public_port' => 'nullable|integer',
|
||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'database.name' => 'Name',
|
||||
'database.description' => 'Description',
|
||||
'database.clickhouse_admin_user' => 'Postgres User',
|
||||
'database.clickhouse_admin_password' => 'Postgres Password',
|
||||
'database.image' => 'Image',
|
||||
'database.ports_mappings' => 'Port Mapping',
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
}
|
||||
public function instantSaveAdvanced() {
|
||||
try {
|
||||
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
return;
|
||||
}
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
if ($this->database->is_public && !$this->database->public_port) {
|
||||
$this->dispatch('error', 'Public port is required.');
|
||||
$this->database->is_public = false;
|
||||
return;
|
||||
}
|
||||
if ($this->database->is_public) {
|
||||
if (!str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->database->is_public = false;
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = null;
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = !$this->database->is_public;
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function refresh(): void
|
||||
{
|
||||
$this->database->refresh();
|
||||
}
|
||||
|
||||
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
if (str($this->database->public_port)->isEmpty()) {
|
||||
$this->database->public_port = null;
|
||||
}
|
||||
$this->validate();
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
if (is_null($this->database->config_hash)) {
|
||||
$this->database->isConfigurationChanged(true);
|
||||
} else {
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,8 @@
|
||||
class Configuration extends Component
|
||||
{
|
||||
public $database;
|
||||
public function mount() {
|
||||
public function mount()
|
||||
{
|
||||
$project = currentTeam()->load(['projects'])->projects->where('uuid', request()->route('project_uuid'))->first();
|
||||
if (!$project) {
|
||||
return redirect()->route('dashboard');
|
||||
@ -21,6 +22,10 @@ public function mount() {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$this->database = $database;
|
||||
if (str($this->database->status)->startsWith('running') && is_null($this->database->config_hash)) {
|
||||
$this->database->isConfigurationChanged(true);
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
|
113
app/Livewire/Project/Database/Dragonfly/General.php
Normal file
113
app/Livewire/Project/Database/Dragonfly/General.php
Normal file
@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Database\Dragonfly;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
|
||||
class General extends Component
|
||||
{
|
||||
protected $listeners = ['refresh'];
|
||||
|
||||
public StandaloneDragonfly $database;
|
||||
public ?string $db_url = null;
|
||||
public ?string $db_url_public = null;
|
||||
|
||||
protected $rules = [
|
||||
'database.name' => 'required',
|
||||
'database.description' => 'nullable',
|
||||
'database.dragonfly_password' => 'required',
|
||||
'database.image' => 'required',
|
||||
'database.ports_mappings' => 'nullable',
|
||||
'database.is_public' => 'nullable|boolean',
|
||||
'database.public_port' => 'nullable|integer',
|
||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'database.name' => 'Name',
|
||||
'database.description' => 'Description',
|
||||
'database.dragonfly_password' => 'Redis Password',
|
||||
'database.image' => 'Image',
|
||||
'database.ports_mappings' => 'Port Mapping',
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
}
|
||||
public function instantSaveAdvanced() {
|
||||
try {
|
||||
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
return;
|
||||
}
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
if (is_null($this->database->config_hash)) {
|
||||
$this->database->isConfigurationChanged(true);
|
||||
} else {
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
if ($this->database->is_public && !$this->database->public_port) {
|
||||
$this->dispatch('error', 'Public port is required.');
|
||||
$this->database->is_public = false;
|
||||
return;
|
||||
}
|
||||
if ($this->database->is_public) {
|
||||
if (!str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->database->is_public = false;
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = null;
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = !$this->database->is_public;
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function refresh(): void
|
||||
{
|
||||
$this->database->refresh();
|
||||
}
|
||||
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.database.dragonfly.general');
|
||||
}
|
||||
}
|
@ -2,13 +2,15 @@
|
||||
|
||||
namespace App\Livewire\Project\Database;
|
||||
|
||||
use App\Actions\Database\StartClickhouse;
|
||||
use App\Actions\Database\StartDragonfly;
|
||||
use App\Actions\Database\StartKeydb;
|
||||
use App\Actions\Database\StartMariadb;
|
||||
use App\Actions\Database\StartMongodb;
|
||||
use App\Actions\Database\StartMysql;
|
||||
use App\Actions\Database\StartPostgresql;
|
||||
use App\Actions\Database\StartRedis;
|
||||
use App\Actions\Database\StopDatabase;
|
||||
use App\Events\DatabaseStatusChanged;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use Livewire\Component;
|
||||
|
||||
@ -32,6 +34,12 @@ public function activityFinished()
|
||||
]);
|
||||
$this->dispatch('refresh');
|
||||
$this->check_status();
|
||||
if (is_null($this->database->config_hash) || $this->database->isConfigurationChanged()) {
|
||||
$this->database->isConfigurationChanged(true);
|
||||
$this->dispatch('configurationChanged');
|
||||
} else {
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
}
|
||||
|
||||
public function check_status($showNotification = false)
|
||||
@ -71,6 +79,15 @@ public function start()
|
||||
} else if ($this->database->type() === 'standalone-mariadb') {
|
||||
$activity = StartMariadb::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} else if ($this->database->type() === 'standalone-keydb') {
|
||||
$activity = StartKeydb::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} else if ($this->database->type() === 'standalone-dragonfly') {
|
||||
$activity = StartDragonfly::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
} else if ($this->database->type() === 'standalone-clickhouse') {
|
||||
$activity = StartClickhouse::run($this->database);
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,29 +2,25 @@
|
||||
|
||||
namespace App\Livewire\Project\Database;
|
||||
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
use Livewire\WithFileUploads;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
|
||||
class Import extends Component
|
||||
{
|
||||
use WithFileUploads;
|
||||
|
||||
public $file;
|
||||
public bool $unsupported = false;
|
||||
public $resource;
|
||||
public $parameters;
|
||||
public $containers;
|
||||
public bool $validated = true;
|
||||
public bool $scpInProgress = false;
|
||||
public bool $importRunning = false;
|
||||
public string $validationMsg = '';
|
||||
|
||||
public ?string $filename = null;
|
||||
public ?string $filesize = null;
|
||||
public bool $isUploading = false;
|
||||
public int $progress = 0;
|
||||
public bool $error = false;
|
||||
|
||||
public Server $server;
|
||||
public string $container;
|
||||
public array $importCommands = [];
|
||||
@ -51,22 +47,9 @@ public function getContainers()
|
||||
if (!data_get($this->parameters, 'database_uuid')) {
|
||||
abort(404);
|
||||
}
|
||||
|
||||
$resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
$resource = getResourceByUuid($this->parameters['database_uuid'], data_get(auth()->user()->currentTeam(), 'id'));
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneMysql::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneMariadb::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
abort(404);
|
||||
}
|
||||
$this->resource = $resource;
|
||||
$this->server = $this->resource->destination->server;
|
||||
@ -75,38 +58,34 @@ public function getContainers()
|
||||
$this->containers->push($this->container);
|
||||
}
|
||||
|
||||
if ($this->containers->count() > 1) {
|
||||
$this->validated = false;
|
||||
$this->validationMsg = 'The database service has more than one container running. Cannot import.';
|
||||
}
|
||||
|
||||
if (
|
||||
$this->resource->getMorphClass() == 'App\Models\StandaloneRedis'
|
||||
|| $this->resource->getMorphClass() == 'App\Models\StandaloneMongodb'
|
||||
$this->resource->getMorphClass() == 'App\Models\StandaloneRedis' ||
|
||||
$this->resource->getMorphClass() == 'App\Models\StandaloneKeydb' ||
|
||||
$this->resource->getMorphClass() == 'App\Models\StandaloneDragonfly' ||
|
||||
$this->resource->getMorphClass() == 'App\Models\StandaloneClickhouse' ||
|
||||
$this->resource->getMorphClass() == 'App\Models\StandaloneMongodb'
|
||||
) {
|
||||
$this->validated = false;
|
||||
$this->validationMsg = 'This database type is not currently supported.';
|
||||
$this->unsupported = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function runImport()
|
||||
{
|
||||
$this->validate([
|
||||
'file' => 'required|file|max:102400'
|
||||
]);
|
||||
|
||||
$this->importRunning = true;
|
||||
$this->scpInProgress = true;
|
||||
|
||||
if ($this->filename == '') {
|
||||
$this->dispatch('error', 'Please select a file to import.');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
$uploadedFilename = $this->file->store('backup-import');
|
||||
$uploadedFilename = "upload/{$this->resource->uuid}/restore";
|
||||
$path = Storage::path($uploadedFilename);
|
||||
if (!Storage::exists($uploadedFilename)) {
|
||||
$this->dispatch('error', 'The file does not exist or has been deleted.');
|
||||
return;
|
||||
}
|
||||
$tmpPath = '/tmp/' . basename($uploadedFilename);
|
||||
|
||||
// SCP the backup file to the server.
|
||||
instant_scp($path, $tmpPath, $this->server);
|
||||
$this->scpInProgress = false;
|
||||
|
||||
Storage::delete($uploadedFilename);
|
||||
$this->importCommands[] = "docker cp {$tmpPath} {$this->container}:{$tmpPath}";
|
||||
|
||||
switch ($this->resource->getMorphClass()) {
|
||||
@ -132,8 +111,7 @@ public function runImport()
|
||||
$this->dispatch('activityMonitor', $activity->id);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$this->validated = false;
|
||||
$this->validationMsg = $e->getMessage();
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
117
app/Livewire/Project/Database/Keydb/General.php
Normal file
117
app/Livewire/Project/Database/Keydb/General.php
Normal file
@ -0,0 +1,117 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Database\Keydb;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
|
||||
class General extends Component
|
||||
{
|
||||
protected $listeners = ['refresh'];
|
||||
|
||||
public StandaloneKeydb $database;
|
||||
public ?string $db_url = null;
|
||||
public ?string $db_url_public = null;
|
||||
|
||||
protected $rules = [
|
||||
'database.name' => 'required',
|
||||
'database.description' => 'nullable',
|
||||
'database.keydb_conf' => 'nullable',
|
||||
'database.keydb_password' => 'required',
|
||||
'database.image' => 'required',
|
||||
'database.ports_mappings' => 'nullable',
|
||||
'database.is_public' => 'nullable|boolean',
|
||||
'database.public_port' => 'nullable|integer',
|
||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'database.name' => 'Name',
|
||||
'database.description' => 'Description',
|
||||
'database.keydb_conf' => 'Redis Configuration',
|
||||
'database.keydb_password' => 'Redis Password',
|
||||
'database.image' => 'Image',
|
||||
'database.ports_mappings' => 'Port Mapping',
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$this->db_url = $this->database->get_db_url(true);
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
}
|
||||
public function instantSaveAdvanced() {
|
||||
try {
|
||||
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
|
||||
return;
|
||||
}
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
if ($this->database->keydb_conf === "") {
|
||||
$this->database->keydb_conf = null;
|
||||
}
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
if (is_null($this->database->config_hash)) {
|
||||
$this->database->isConfigurationChanged(true);
|
||||
} else {
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
if ($this->database->is_public && !$this->database->public_port) {
|
||||
$this->dispatch('error', 'Public port is required.');
|
||||
$this->database->is_public = false;
|
||||
return;
|
||||
}
|
||||
if ($this->database->is_public) {
|
||||
if (!str($this->database->status)->startsWith('running')) {
|
||||
$this->dispatch('error', 'Database must be started to be publicly accessible.');
|
||||
$this->database->is_public = false;
|
||||
return;
|
||||
}
|
||||
StartDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
$this->dispatch('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
StopDatabaseProxy::run($this->database);
|
||||
$this->db_url_public = null;
|
||||
$this->dispatch('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->database->save();
|
||||
} catch (\Throwable $e) {
|
||||
$this->database->is_public = !$this->database->is_public;
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function refresh(): void
|
||||
{
|
||||
$this->database->refresh();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.database.keydb.general');
|
||||
}
|
||||
}
|
@ -76,6 +76,12 @@ public function submit()
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
if (is_null($this->database->config_hash)) {
|
||||
$this->database->isConfigurationChanged(true);
|
||||
} else {
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
|
@ -78,6 +78,12 @@ public function submit()
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
if (is_null($this->database->config_hash)) {
|
||||
$this->database->isConfigurationChanged(true);
|
||||
} else {
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
|
@ -77,6 +77,12 @@ public function submit()
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
if (is_null($this->database->config_hash)) {
|
||||
$this->database->isConfigurationChanged(true);
|
||||
} else {
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
|
@ -58,7 +58,8 @@ public function mount()
|
||||
$this->db_url_public = $this->database->get_db_url();
|
||||
}
|
||||
}
|
||||
public function instantSaveAdvanced() {
|
||||
public function instantSaveAdvanced()
|
||||
{
|
||||
try {
|
||||
if (!$this->database->destination->server->isLogDrainEnabled()) {
|
||||
$this->database->is_log_drain_enabled = false;
|
||||
@ -164,6 +165,12 @@ public function submit()
|
||||
$this->dispatch('success', 'Database updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
if (is_null($this->database->config_hash)) {
|
||||
$this->database->isConfigurationChanged(true);
|
||||
} else {
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -120,6 +120,9 @@ public function setType(string $type)
|
||||
case 'mysql':
|
||||
case 'mariadb':
|
||||
case 'redis':
|
||||
case 'keydb':
|
||||
case 'dragonfly':
|
||||
case 'clickhouse':
|
||||
case 'mongodb':
|
||||
$this->isDatabase = true;
|
||||
$this->includeSwarm = false;
|
||||
|
@ -36,6 +36,12 @@ public function mount()
|
||||
$database = create_standalone_mysql($environment->id, $destination_uuid);
|
||||
} else if ($type->value() === 'mariadb') {
|
||||
$database = create_standalone_mariadb($environment->id, $destination_uuid);
|
||||
} else if ($type->value() === 'keydb') {
|
||||
$database = create_standalone_keydb($environment->id, $destination_uuid);
|
||||
} else if ($type->value() === 'dragonfly') {
|
||||
$database = create_standalone_dragonfly($environment->id, $destination_uuid);
|
||||
}else if ($type->value() === 'clickhouse') {
|
||||
$database = create_standalone_clickhouse($environment->id, $destination_uuid);
|
||||
}
|
||||
return redirect()->route('project.database.configuration', [
|
||||
'project_uuid' => $project->uuid,
|
||||
|
@ -16,6 +16,9 @@ class Index extends Component
|
||||
public $mongodbs = [];
|
||||
public $mysqls = [];
|
||||
public $mariadbs = [];
|
||||
public $keydbs = [];
|
||||
public $dragonflies = [];
|
||||
public $clickhouses = [];
|
||||
public $services = [];
|
||||
public function mount()
|
||||
{
|
||||
@ -96,6 +99,39 @@ public function mount()
|
||||
}
|
||||
return $mariadb;
|
||||
});
|
||||
$this->keydbs = $this->environment->keydbs->load(['tags'])->sortBy('name');
|
||||
$this->keydbs = $this->keydbs->map(function ($keydb) {
|
||||
if (data_get($keydb, 'environment.project.uuid')) {
|
||||
$keydb->hrefLink = route('project.database.configuration', [
|
||||
'project_uuid' => data_get($keydb, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($keydb, 'environment.name'),
|
||||
'database_uuid' => data_get($keydb, 'uuid')
|
||||
]);
|
||||
}
|
||||
return $keydb;
|
||||
});
|
||||
$this->dragonflies = $this->environment->dragonflies->load(['tags'])->sortBy('name');
|
||||
$this->dragonflies = $this->dragonflies->map(function ($dragonfly) {
|
||||
if (data_get($dragonfly, 'environment.project.uuid')) {
|
||||
$dragonfly->hrefLink = route('project.database.configuration', [
|
||||
'project_uuid' => data_get($dragonfly, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($dragonfly, 'environment.name'),
|
||||
'database_uuid' => data_get($dragonfly, 'uuid')
|
||||
]);
|
||||
}
|
||||
return $dragonfly;
|
||||
});
|
||||
$this->clickhouses = $this->environment->clickhouses->load(['tags'])->sortBy('name');
|
||||
$this->clickhouses = $this->clickhouses->map(function ($clickhouse) {
|
||||
if (data_get($clickhouse, 'environment.project.uuid')) {
|
||||
$clickhouse->hrefLink = route('project.database.configuration', [
|
||||
'project_uuid' => data_get($clickhouse, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($clickhouse, 'environment.name'),
|
||||
'database_uuid' => data_get($clickhouse, 'uuid')
|
||||
]);
|
||||
}
|
||||
return $clickhouse;
|
||||
});
|
||||
$this->services = $this->environment->services->load(['tags'])->sortBy('name');
|
||||
$this->services = $this->services->map(function ($service) {
|
||||
if (data_get($service, 'environment.project.uuid')) {
|
||||
|
@ -18,7 +18,8 @@ public function getListeners()
|
||||
$userId = auth()->user()->id;
|
||||
return [
|
||||
"echo-private:user.{$userId},ServiceStatusChanged" => 'check_status',
|
||||
"check_status"
|
||||
"check_status",
|
||||
"refresh" => '$refresh',
|
||||
];
|
||||
}
|
||||
public function render()
|
||||
@ -65,7 +66,7 @@ public function check_status()
|
||||
try {
|
||||
dispatch_sync(new ContainerStatusJob($this->service->server));
|
||||
$this->dispatch('refresh')->self();
|
||||
$this->dispatch('serviceStatusChanged');
|
||||
$this->dispatch('updateStatus');
|
||||
} catch (\Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ public function mount() {
|
||||
}
|
||||
|
||||
public function saveEditedCompose() {
|
||||
$this->dispatch('warning', "Saving new docker compose...");
|
||||
$this->dispatch('info', "Saving new docker compose...");
|
||||
$this->dispatch('saveCompose', $this->service->docker_compose_raw);
|
||||
}
|
||||
public function render()
|
||||
|
53
app/Livewire/Project/Service/EditDomain.php
Normal file
53
app/Livewire/Project/Service/EditDomain.php
Normal file
@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Service;
|
||||
|
||||
use App\Models\ServiceApplication;
|
||||
use Livewire\Component;
|
||||
|
||||
class EditDomain extends Component
|
||||
{
|
||||
public $applicationId;
|
||||
public ServiceApplication $application;
|
||||
protected $rules = [
|
||||
'application.fqdn' => 'nullable',
|
||||
'application.required_fqdn' => 'required|boolean',
|
||||
];
|
||||
public function mount() {
|
||||
$this->application = ServiceApplication::find($this->applicationId);
|
||||
}
|
||||
public function updatedApplicationFqdn()
|
||||
{
|
||||
$this->application->fqdn = str($this->application->fqdn)->replaceEnd(',', '')->trim();
|
||||
$this->application->fqdn = str($this->application->fqdn)->replaceStart(',', '')->trim();
|
||||
$this->application->fqdn = str($this->application->fqdn)->trim()->explode(',')->map(function ($domain) {
|
||||
return str($domain)->trim()->lower();
|
||||
});
|
||||
$this->application->fqdn = $this->application->fqdn->unique()->implode(',');
|
||||
$this->application->save();
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
check_domain_usage(resource: $this->application);
|
||||
$this->validate();
|
||||
$this->application->save();
|
||||
updateCompose($this->application);
|
||||
if (str($this->application->fqdn)->contains(',')) {
|
||||
$this->dispatch('warning', 'Some services do not support multiple domains, which can lead to problems and is NOT RECOMMENDED.<br><br>Only use multiple domains if you know what you are doing.');
|
||||
} else {
|
||||
$this->dispatch('success', 'Service saved.');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->dispatch('generateDockerCompose');
|
||||
$this->dispatch('refresh');
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.service.edit-domain');
|
||||
}
|
||||
}
|
@ -5,13 +5,14 @@
|
||||
use App\Models\LocalFileVolume;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class FileStorage extends Component
|
||||
{
|
||||
public LocalFileVolume $fileStorage;
|
||||
public ServiceApplication|ServiceDatabase $service;
|
||||
public ServiceApplication|ServiceDatabase|StandaloneClickhouse $resource;
|
||||
public string $fs_path;
|
||||
public ?string $workdir = null;
|
||||
|
||||
@ -23,14 +24,14 @@ class FileStorage extends Component
|
||||
];
|
||||
public function mount()
|
||||
{
|
||||
$this->service = $this->fileStorage->service;
|
||||
if (Str::of($this->fileStorage->fs_path)->startsWith('.')) {
|
||||
$this->workdir = $this->service->service->workdir();
|
||||
$this->resource = $this->fileStorage->service;
|
||||
if (Str::of($this->fileStorage->fs_path)->startsWith('.')) {
|
||||
$this->workdir = $this->resource->service->workdir();
|
||||
$this->fs_path = Str::of($this->fileStorage->fs_path)->after('.');
|
||||
} else {
|
||||
} else {
|
||||
$this->workdir = null;
|
||||
$this->fs_path = $this->fileStorage->fs_path;
|
||||
}
|
||||
}
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
|
@ -17,21 +17,33 @@ class Navbar extends Component
|
||||
public array $query;
|
||||
public $isDeploymentProgress = false;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if (str($this->service->status())->contains('running') && is_null($this->service->config_hash)) {
|
||||
ray('isConfigurationChanged init');
|
||||
$this->service->isConfigurationChanged(true);
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
}
|
||||
public function getListeners()
|
||||
{
|
||||
$userId = auth()->user()->id;
|
||||
return [
|
||||
"echo-private:user.{$userId},ServiceStatusChanged" => 'serviceStarted',
|
||||
"serviceStatusChanged"
|
||||
"updateStatus"=> '$refresh',
|
||||
];
|
||||
}
|
||||
public function serviceStarted() {
|
||||
$this->dispatch('success', 'Service status changed.');
|
||||
}
|
||||
public function serviceStatusChanged()
|
||||
public function serviceStarted()
|
||||
{
|
||||
$this->dispatch('refresh')->self();
|
||||
$this->dispatch('success', 'Service status changed.');
|
||||
if (is_null($this->service->config_hash) || $this->service->isConfigurationChanged()) {
|
||||
$this->service->isConfigurationChanged(true);
|
||||
$this->dispatch('configurationChanged');
|
||||
} else {
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
}
|
||||
|
||||
public function check_status()
|
||||
{
|
||||
$this->dispatch('check_status');
|
||||
|
@ -69,6 +69,13 @@ public function submit()
|
||||
$this->dispatch('success', 'Service saved.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
if (is_null($this->service->config_hash)) {
|
||||
ray('asdf');
|
||||
$this->service->isConfigurationChanged(true);
|
||||
} else {
|
||||
$this->dispatch('configurationChanged');
|
||||
}
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
|
34
app/Livewire/Project/Shared/ConfigurationChecker.php
Normal file
34
app/Livewire/Project/Shared/ConfigurationChecker.php
Normal file
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Shared;
|
||||
|
||||
use App\Models\Application;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Livewire\Component;
|
||||
|
||||
class ConfigurationChecker extends Component
|
||||
{
|
||||
public bool $isConfigurationChanged = false;
|
||||
public Application|Service|StandaloneRedis|StandalonePostgresql|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource;
|
||||
protected $listeners = ['configurationChanged'];
|
||||
public function mount()
|
||||
{
|
||||
$this->configurationChanged();
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.shared.configuration-checker');
|
||||
}
|
||||
public function configurationChanged()
|
||||
{
|
||||
$this->isConfigurationChanged = $this->resource->isConfigurationChanged();
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@ class Danger extends Component
|
||||
public $resource;
|
||||
public $projectUuid;
|
||||
public $environmentName;
|
||||
public bool $delete_configurations = true;
|
||||
public ?string $modalId = null;
|
||||
|
||||
public function mount()
|
||||
@ -20,12 +21,11 @@ public function mount()
|
||||
$this->projectUuid = data_get($parameters, 'project_uuid');
|
||||
$this->environmentName = data_get($parameters, 'environment_name');
|
||||
}
|
||||
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
$this->resource->delete();
|
||||
DeleteResourceJob::dispatch($this->resource);
|
||||
DeleteResourceJob::dispatch($this->resource, $this->delete_configurations);
|
||||
return redirect()->route('project.resource.index', [
|
||||
'project_uuid' => $this->projectUuid,
|
||||
'environment_name' => $this->environmentName
|
||||
|
@ -119,6 +119,15 @@ public function saveVariables($isPreview)
|
||||
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;
|
||||
@ -173,6 +182,15 @@ public function submit($data)
|
||||
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;
|
||||
|
@ -5,11 +5,6 @@
|
||||
use App\Models\Application;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
|
||||
@ -50,21 +45,9 @@ public function mount()
|
||||
}
|
||||
} else if (data_get($this->parameters, 'database_uuid')) {
|
||||
$this->type = 'database';
|
||||
$resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
$resource = getResourceByUuid($this->parameters['database_uuid'], data_get(auth()->user()->currentTeam(),'id'));
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneMysql::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneMariadb::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
abort(404);
|
||||
}
|
||||
$this->resource = $resource;
|
||||
if ($this->resource->destination->server->isFunctional()) {
|
||||
@ -109,7 +92,7 @@ public function loadContainers()
|
||||
];
|
||||
$this->containers = $this->containers->push($payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($this->containers->count() > 0) {
|
||||
if (data_get($this->parameters, 'application_uuid')) {
|
||||
|
@ -7,6 +7,9 @@
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
@ -19,7 +22,7 @@ class GetLogs extends Component
|
||||
{
|
||||
public string $outputs = '';
|
||||
public string $errors = '';
|
||||
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|null $resource = null;
|
||||
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse|null $resource = null;
|
||||
public ServiceApplication|ServiceDatabase|null $servicesubtype = null;
|
||||
public Server $server;
|
||||
public ?string $container = null;
|
||||
|
@ -5,6 +5,9 @@
|
||||
use App\Models\Application;
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
@ -16,7 +19,7 @@
|
||||
class Logs extends Component
|
||||
{
|
||||
public ?string $type = null;
|
||||
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource;
|
||||
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb|StandaloneKeydb|StandaloneDragonfly|StandaloneClickhouse $resource;
|
||||
public Collection $servers;
|
||||
public Collection $containers;
|
||||
public $container = [];
|
||||
@ -67,21 +70,9 @@ public function mount()
|
||||
}
|
||||
} else if (data_get($this->parameters, 'database_uuid')) {
|
||||
$this->type = 'database';
|
||||
$resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
$resource = getResourceByUuid($this->parameters['database_uuid'], data_get(auth()->user()->currentTeam(), 'id'));
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneMongodb::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneMysql::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneMariadb::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
abort(404);
|
||||
}
|
||||
$this->resource = $resource;
|
||||
$this->status = $this->resource->status;
|
||||
|
@ -76,7 +76,10 @@ public function cloneTo($destination_id)
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneMongodb' ||
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneMysql' ||
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneMariadb' ||
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneRedis'
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneRedis' ||
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneKeydb' ||
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneDragonfly' ||
|
||||
$this->resource->getMorphClass() === 'App\Models\StandaloneClickhouse'
|
||||
) {
|
||||
$uuid = (string)new Cuid2(7);
|
||||
$new_resource = $this->resource->replicate()->fill([
|
||||
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Livewire\Project\Shared\Storages;
|
||||
|
||||
use App\Models\LocalPersistentVolume;
|
||||
use Livewire\Component;
|
||||
|
||||
class All extends Component
|
||||
|
@ -75,6 +75,7 @@ public function revalidate()
|
||||
}
|
||||
public function checkLocalhostConnection()
|
||||
{
|
||||
$this->submit();
|
||||
$uptime = $this->server->validateConnection();
|
||||
if ($uptime) {
|
||||
$this->dispatch('success', 'Server is reachable.');
|
||||
|
@ -57,6 +57,15 @@ protected static function booted()
|
||||
});
|
||||
}
|
||||
|
||||
public function delete_configurations()
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
$workdir = $this->workdir();
|
||||
if (str($workdir)->endsWith($this->uuid)) {
|
||||
ray('Deleting workdir');
|
||||
instant_remote_process(["rm -rf " . $this->workdir()], $server, false);
|
||||
}
|
||||
}
|
||||
public function additional_servers()
|
||||
{
|
||||
return $this->belongsToMany(Server::class, 'additional_destinations')
|
||||
@ -500,7 +509,7 @@ public function isLogDrainEnabled()
|
||||
}
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->port_exposes . $this->port_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels;
|
||||
$newConfigHash = $this->fqdn . $this->git_repository . $this->git_branch . $this->git_commit_sha . $this->build_pack . $this->static_image . $this->install_command . $this->build_command . $this->start_command . $this->ports_exposes . $this->ports_mappings . $this->base_directory . $this->publish_directory . $this->dockerfile . $this->dockerfile_location . $this->custom_labels . $this->custom_docker_run_options . $this->dockerfile_target_build;
|
||||
if ($this->pull_request_id === 0 || $this->pull_request_id === null) {
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
|
||||
} else {
|
||||
|
@ -46,7 +46,18 @@ public function mariadbs()
|
||||
{
|
||||
return $this->hasMany(StandaloneMariadb::class);
|
||||
}
|
||||
|
||||
public function keydbs()
|
||||
{
|
||||
return $this->hasMany(StandaloneKeydb::class);
|
||||
}
|
||||
public function dragonflies()
|
||||
{
|
||||
return $this->hasMany(StandaloneDragonfly::class);
|
||||
}
|
||||
public function clickhouses()
|
||||
{
|
||||
return $this->hasMany(StandaloneClickhouse::class);
|
||||
}
|
||||
public function databases()
|
||||
{
|
||||
$postgresqls = $this->postgresqls;
|
||||
@ -54,7 +65,10 @@ public function databases()
|
||||
$mongodbs = $this->mongodbs;
|
||||
$mysqls = $this->mysqls;
|
||||
$mariadbs = $this->mariadbs;
|
||||
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
|
||||
$keydbs = $this->keydbs;
|
||||
$dragonflies = $this->dragonflies;
|
||||
$clickhouses = $this->clickhouses;
|
||||
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs)->concat($keydbs)->concat($dragonflies)->concat($clickhouses);
|
||||
}
|
||||
|
||||
public function project()
|
||||
|
@ -32,7 +32,7 @@ protected static function booted()
|
||||
'key' => $environment_variable->key,
|
||||
'value' => $environment_variable->value,
|
||||
'is_build_time' => $environment_variable->is_build_time,
|
||||
'is_multiline' => $environment_variable->is_multiline,
|
||||
'is_multiline' => $environment_variable->is_multiline ?? false,
|
||||
'application_id' => $environment_variable->application_id,
|
||||
'is_preview' => true
|
||||
]);
|
||||
@ -63,19 +63,7 @@ public function resource()
|
||||
} else if ($this->service_id) {
|
||||
$resource = Service::find($this->service_id);
|
||||
} else if ($this->database_id) {
|
||||
$resource = StandalonePostgresql::find($this->database_id);
|
||||
if (!$resource) {
|
||||
$resource = StandaloneMysql::find($this->database_id);
|
||||
if (!$resource) {
|
||||
$resource = StandaloneRedis::find($this->database_id);
|
||||
if (!$resource) {
|
||||
$resource = StandaloneMongodb::find($this->database_id);
|
||||
if (!$resource) {
|
||||
$resource = StandaloneMariadb::find($this->database_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$resource = getResourceByUuid($this->parameters['database_uuid'], data_get(auth()->user()->currentTeam(), 'id'));
|
||||
}
|
||||
return $resource;
|
||||
}
|
||||
|
@ -22,8 +22,14 @@ public function service()
|
||||
}
|
||||
public function saveStorageOnServer()
|
||||
{
|
||||
$workdir = $this->resource->service->workdir();
|
||||
$server = $this->resource->service->server;
|
||||
$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([
|
||||
"mkdir -p $workdir > /dev/null 2>&1 || true",
|
||||
"cd $workdir"
|
||||
@ -55,8 +61,18 @@ public function saveStorageOnServer()
|
||||
if (!$fileVolume->is_directory && $isDir == 'NOK') {
|
||||
if ($content) {
|
||||
$content = base64_encode($content);
|
||||
$chmod = $fileVolume->chmod;
|
||||
$chown = $fileVolume->chown;
|
||||
ray($content, $path, $chmod, $chown);
|
||||
$commands->push("echo '$content' | base64 -d > $path");
|
||||
$commands->push("chmod +x $path");
|
||||
if ($chown) {
|
||||
$commands->push("chown $chown $path");
|
||||
}
|
||||
if ($chmod) {
|
||||
$commands->push("chmod $chmod $path");
|
||||
}
|
||||
|
||||
}
|
||||
} else if ($isDir == 'NOK' && $fileVolume->is_directory) {
|
||||
$commands->push("mkdir -p $path > /dev/null 2>&1 || true");
|
||||
|
@ -63,6 +63,18 @@ public function redis()
|
||||
{
|
||||
return $this->hasManyThrough(StandaloneRedis::class, Environment::class);
|
||||
}
|
||||
public function keydbs()
|
||||
{
|
||||
return $this->hasManyThrough(StandaloneKeydb::class, Environment::class);
|
||||
}
|
||||
public function dragonflies()
|
||||
{
|
||||
return $this->hasManyThrough(StandaloneDragonfly::class, Environment::class);
|
||||
}
|
||||
public function clickhouses()
|
||||
{
|
||||
return $this->hasManyThrough(StandaloneClickhouse::class, Environment::class);
|
||||
}
|
||||
public function mongodbs()
|
||||
{
|
||||
return $this->hasManyThrough(StandaloneMongodb::class, Environment::class);
|
||||
@ -77,6 +89,6 @@ public function mariadbs()
|
||||
}
|
||||
public function resource_count()
|
||||
{
|
||||
return $this->applications()->count() + $this->postgresqls()->count() + $this->redis()->count() + $this->mongodbs()->count() + $this->mysqls()->count() + $this->mariadbs()->count();
|
||||
return $this->applications()->count() + $this->postgresqls()->count() + $this->redis()->count() + $this->mongodbs()->count() + $this->mysqls()->count() + $this->mariadbs()->count() + $this->keydbs()->count() + $this->dragonflies()->count() + $this->services()->count() + $this->clickhouses()->count();
|
||||
}
|
||||
}
|
||||
|
@ -585,7 +585,10 @@ public function databases()
|
||||
$mongodbs = data_get($standaloneDocker, 'mongodbs', collect([]));
|
||||
$mysqls = data_get($standaloneDocker, 'mysqls', collect([]));
|
||||
$mariadbs = data_get($standaloneDocker, 'mariadbs', collect([]));
|
||||
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
|
||||
$keydbs = data_get($standaloneDocker, 'keydbs', collect([]));
|
||||
$dragonflies = data_get($standaloneDocker, 'dragonflies', collect([]));
|
||||
$clickhouses = data_get($standaloneDocker, 'clickhouses', collect([]));
|
||||
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs)->concat($keydbs)->concat($dragonflies)->concat($clickhouses);
|
||||
})->filter(function ($item) {
|
||||
return data_get($item, 'name') !== 'coolify-db';
|
||||
})->flatten();
|
||||
|
@ -11,6 +11,46 @@ class Service extends BaseModel
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
protected $guarded = [];
|
||||
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$domains = $this->applications()->get()->pluck('fqdn')->toArray();
|
||||
$domains = implode(',', $domains);
|
||||
|
||||
$applicationImages = $this->applications()->get()->pluck('image');
|
||||
$databaseImages = $this->databases()->get()->pluck('image');
|
||||
$images = $applicationImages->merge($databaseImages);
|
||||
$images = implode(',', $images->toArray());
|
||||
|
||||
$applicationStorages = $this->applications()->get()->pluck('persistentStorages')->flatten();
|
||||
$databaseStorages = $this->databases()->get()->pluck('persistentStorages')->flatten();
|
||||
$storages = $applicationStorages->merge($databaseStorages)->implode('updated_at');
|
||||
|
||||
$newConfigHash = $images . $domains . $images . $storages;
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get('value'));
|
||||
$newConfigHash = md5($newConfigHash);
|
||||
$oldConfigHash = data_get($this, 'config_hash');
|
||||
if ($oldConfigHash === null) {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if ($oldConfigHash === $newConfigHash) {
|
||||
return false;
|
||||
} else {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status())->contains('exited');
|
||||
}
|
||||
public function type()
|
||||
{
|
||||
return 'service';
|
||||
@ -27,6 +67,14 @@ public function tags()
|
||||
{
|
||||
return $this->morphToMany(Tag::class, 'taggable');
|
||||
}
|
||||
public function delete_configurations()
|
||||
{
|
||||
$server = data_get($this, 'server');
|
||||
$workdir = $this->workdir();
|
||||
if (str($workdir)->endsWith($this->uuid)) {
|
||||
instant_remote_process(["rm -rf " . $this->workdir()], $server, false);
|
||||
}
|
||||
}
|
||||
public function status()
|
||||
{
|
||||
$applications = $this->applications;
|
||||
|
210
app/Models/StandaloneClickhouse.php
Normal file
210
app/Models/StandaloneClickhouse.php
Normal file
@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandaloneClickhouse extends BaseModel
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
|
||||
protected $guarded = [];
|
||||
protected $casts = [
|
||||
'clickhouse_password' => 'encrypted',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function ($database) {
|
||||
LocalPersistentVolume::create([
|
||||
'name' => 'clickhouse-data-' . $database->uuid,
|
||||
'mount_path' => '/bitnami/clickhouse',
|
||||
'host_path' => null,
|
||||
'resource_id' => $database->id,
|
||||
'resource_type' => $database->getMorphClass(),
|
||||
'is_readonly' => true
|
||||
]);
|
||||
});
|
||||
static::deleting(function ($database) {
|
||||
$storages = $database->persistentStorages()->get();
|
||||
$server = data_get($database, 'destination.server');
|
||||
if ($server) {
|
||||
foreach ($storages as $storage) {
|
||||
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||
}
|
||||
}
|
||||
$database->scheduledBackups()->delete();
|
||||
$database->persistentStorages()->delete();
|
||||
$database->environment_variables()->delete();
|
||||
$database->tags()->detach();
|
||||
});
|
||||
}
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->image . $this->ports_mappings;
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
|
||||
$newConfigHash = md5($newConfigHash);
|
||||
$oldConfigHash = data_get($this, 'config_hash');
|
||||
if ($oldConfigHash === null) {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if ($oldConfigHash === $newConfigHash) {
|
||||
return false;
|
||||
} else {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
}
|
||||
public function workdir()
|
||||
{
|
||||
return database_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
public function delete_configurations()
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
$workdir = $this->workdir();
|
||||
if (str($workdir)->endsWith($this->uuid)) {
|
||||
instant_remote_process(["rm -rf " . $this->workdir()], $server, false);
|
||||
}
|
||||
}
|
||||
public function realStatus()
|
||||
{
|
||||
return $this->getRawOriginal('status');
|
||||
}
|
||||
public function status(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: function ($value) {
|
||||
if (str($value)->contains('(')) {
|
||||
$status = str($value)->before('(')->trim()->value();
|
||||
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||
} else if (str($value)->contains(':')) {
|
||||
$status = str($value)->before(':')->trim()->value();
|
||||
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||
} else {
|
||||
$status = $value;
|
||||
$health = 'unhealthy';
|
||||
}
|
||||
return "$status:$health";
|
||||
},
|
||||
get: function ($value) {
|
||||
if (str($value)->contains('(')) {
|
||||
$status = str($value)->before('(')->trim()->value();
|
||||
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||
} else if (str($value)->contains(':')) {
|
||||
$status = str($value)->before(':')->trim()->value();
|
||||
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||
} else {
|
||||
$status = $value;
|
||||
$health = 'unhealthy';
|
||||
}
|
||||
return "$status:$health";
|
||||
},
|
||||
);
|
||||
}
|
||||
public function tags()
|
||||
{
|
||||
return $this->morphToMany(Tag::class, 'taggable');
|
||||
}
|
||||
public function project()
|
||||
{
|
||||
return data_get($this, 'environment.project');
|
||||
}
|
||||
public function link()
|
||||
{
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
return route('project.database.configuration', [
|
||||
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($this, 'environment.name'),
|
||||
'database_uuid' => data_get($this, 'uuid')
|
||||
]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function isLogDrainEnabled()
|
||||
{
|
||||
return data_get($this, 'is_log_drain_enabled', false);
|
||||
}
|
||||
|
||||
public function portsMappings(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => $value === "" ? null : $value,
|
||||
);
|
||||
}
|
||||
|
||||
public function portsMappingsArray(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => is_null($this->ports_mappings)
|
||||
? []
|
||||
: explode(',', $this->ports_mappings),
|
||||
|
||||
);
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
}
|
||||
public function type(): string
|
||||
{
|
||||
return 'standalone-clickhouse';
|
||||
}
|
||||
public function get_db_url(bool $useInternal = false): string
|
||||
{
|
||||
if ($this->is_public && !$useInternal) {
|
||||
return "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->destination->server->getIp}:{$this->public_port}/{$this->clickhouse_db}";
|
||||
} else {
|
||||
return "clickhouse://{$this->clickhouse_user}:{$this->clickhouse_password}@{$this->uuid}:9000/{$this->clickhouse_db}";
|
||||
}
|
||||
}
|
||||
|
||||
public function environment()
|
||||
{
|
||||
return $this->belongsTo(Environment::class);
|
||||
}
|
||||
|
||||
public function fileStorages()
|
||||
{
|
||||
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||
}
|
||||
|
||||
public function destination()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function environment_variables(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class);
|
||||
}
|
||||
|
||||
public function runtime_environment_variables(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class);
|
||||
}
|
||||
|
||||
public function persistentStorages()
|
||||
{
|
||||
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||
}
|
||||
|
||||
public function scheduledBackups()
|
||||
{
|
||||
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||
}
|
||||
}
|
@ -32,6 +32,18 @@ public function mariadbs()
|
||||
{
|
||||
return $this->morphMany(StandaloneMariadb::class, 'destination');
|
||||
}
|
||||
public function keydbs()
|
||||
{
|
||||
return $this->morphMany(StandaloneKeydb::class, 'destination');
|
||||
}
|
||||
public function dragonflies()
|
||||
{
|
||||
return $this->morphMany(StandaloneDragonfly::class, 'destination');
|
||||
}
|
||||
public function clickhouses()
|
||||
{
|
||||
return $this->morphMany(StandaloneClickhouse::class, 'destination');
|
||||
}
|
||||
|
||||
public function server()
|
||||
{
|
||||
|
210
app/Models/StandaloneDragonfly.php
Normal file
210
app/Models/StandaloneDragonfly.php
Normal file
@ -0,0 +1,210 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandaloneDragonfly extends BaseModel
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
protected $guarded = [];
|
||||
protected $casts = [
|
||||
'dragonfly_password' => 'encrypted',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function ($database) {
|
||||
LocalPersistentVolume::create([
|
||||
'name' => 'dragonfly-data-' . $database->uuid,
|
||||
'mount_path' => '/data',
|
||||
'host_path' => null,
|
||||
'resource_id' => $database->id,
|
||||
'resource_type' => $database->getMorphClass(),
|
||||
'is_readonly' => true
|
||||
]);
|
||||
});
|
||||
static::deleting(function ($database) {
|
||||
$database->scheduledBackups()->delete();
|
||||
$storages = $database->persistentStorages()->get();
|
||||
$server = data_get($database, 'destination.server');
|
||||
if ($server) {
|
||||
foreach ($storages as $storage) {
|
||||
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||
}
|
||||
}
|
||||
$database->persistentStorages()->delete();
|
||||
$database->environment_variables()->delete();
|
||||
$database->tags()->detach();
|
||||
});
|
||||
}
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->image . $this->ports_mappings;
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
|
||||
$newConfigHash = md5($newConfigHash);
|
||||
$oldConfigHash = data_get($this, 'config_hash');
|
||||
if ($oldConfigHash === null) {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if ($oldConfigHash === $newConfigHash) {
|
||||
return false;
|
||||
} else {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
}
|
||||
public function workdir()
|
||||
{
|
||||
return database_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
public function delete_configurations()
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
$workdir = $this->workdir();
|
||||
if (str($workdir)->endsWith($this->uuid)) {
|
||||
instant_remote_process(["rm -rf " . $this->workdir()], $server, false);
|
||||
}
|
||||
}
|
||||
public function realStatus()
|
||||
{
|
||||
return $this->getRawOriginal('status');
|
||||
}
|
||||
public function status(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: function ($value) {
|
||||
if (str($value)->contains('(')) {
|
||||
$status = str($value)->before('(')->trim()->value();
|
||||
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||
} else if (str($value)->contains(':')) {
|
||||
$status = str($value)->before(':')->trim()->value();
|
||||
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||
} else {
|
||||
$status = $value;
|
||||
$health = 'unhealthy';
|
||||
}
|
||||
return "$status:$health";
|
||||
},
|
||||
get: function ($value) {
|
||||
if (str($value)->contains('(')) {
|
||||
$status = str($value)->before('(')->trim()->value();
|
||||
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||
} else if (str($value)->contains(':')) {
|
||||
$status = str($value)->before(':')->trim()->value();
|
||||
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||
} else {
|
||||
$status = $value;
|
||||
$health = 'unhealthy';
|
||||
}
|
||||
return "$status:$health";
|
||||
},
|
||||
);
|
||||
}
|
||||
public function tags()
|
||||
{
|
||||
return $this->morphToMany(Tag::class, 'taggable');
|
||||
}
|
||||
public function project()
|
||||
{
|
||||
return data_get($this, 'environment.project');
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
}
|
||||
public function link()
|
||||
{
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
return route('project.database.configuration', [
|
||||
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($this, 'environment.name'),
|
||||
'database_uuid' => data_get($this, 'uuid')
|
||||
]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function isLogDrainEnabled()
|
||||
{
|
||||
return data_get($this, 'is_log_drain_enabled', false);
|
||||
}
|
||||
|
||||
public function portsMappings(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => $value === "" ? null : $value,
|
||||
);
|
||||
}
|
||||
|
||||
public function portsMappingsArray(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => is_null($this->ports_mappings)
|
||||
? []
|
||||
: explode(',', $this->ports_mappings),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
public function type(): string
|
||||
{
|
||||
return 'standalone-dragonfly';
|
||||
}
|
||||
public function get_db_url(bool $useInternal = false): string
|
||||
{
|
||||
if ($this->is_public && !$useInternal) {
|
||||
return "redis://{$this->dragonfly_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
||||
} else {
|
||||
return "redis://{$this->dragonfly_password}@{$this->uuid}:6379/0";
|
||||
}
|
||||
}
|
||||
|
||||
public function environment()
|
||||
{
|
||||
return $this->belongsTo(Environment::class);
|
||||
}
|
||||
|
||||
public function fileStorages()
|
||||
{
|
||||
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||
}
|
||||
|
||||
public function destination()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function environment_variables(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class);
|
||||
}
|
||||
|
||||
public function runtime_environment_variables(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class);
|
||||
}
|
||||
|
||||
public function persistentStorages()
|
||||
{
|
||||
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||
}
|
||||
|
||||
public function scheduledBackups()
|
||||
{
|
||||
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||
}
|
||||
}
|
212
app/Models/StandaloneKeydb.php
Normal file
212
app/Models/StandaloneKeydb.php
Normal file
@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
|
||||
class StandaloneKeydb extends BaseModel
|
||||
{
|
||||
use HasFactory, SoftDeletes;
|
||||
protected $guarded = [];
|
||||
protected $casts = [
|
||||
'keydb_password' => 'encrypted',
|
||||
];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function ($database) {
|
||||
LocalPersistentVolume::create([
|
||||
'name' => 'keydb-data-' . $database->uuid,
|
||||
'mount_path' => '/data',
|
||||
'host_path' => null,
|
||||
'resource_id' => $database->id,
|
||||
'resource_type' => $database->getMorphClass(),
|
||||
'is_readonly' => true
|
||||
]);
|
||||
});
|
||||
static::deleting(function ($database) {
|
||||
$database->scheduledBackups()->delete();
|
||||
$storages = $database->persistentStorages()->get();
|
||||
$server = data_get($database, 'destination.server');
|
||||
if ($server) {
|
||||
foreach ($storages as $storage) {
|
||||
instant_remote_process(["docker volume rm -f $storage->name"], $server, false);
|
||||
}
|
||||
}
|
||||
$database->persistentStorages()->delete();
|
||||
$database->environment_variables()->delete();
|
||||
$database->tags()->detach();
|
||||
});
|
||||
}
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->image . $this->ports_mappings . $this->keydb_conf;
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
|
||||
$newConfigHash = md5($newConfigHash);
|
||||
$oldConfigHash = data_get($this, 'config_hash');
|
||||
if ($oldConfigHash === null) {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if ($oldConfigHash === $newConfigHash) {
|
||||
return false;
|
||||
} else {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
}
|
||||
public function workdir()
|
||||
{
|
||||
return database_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
public function delete_configurations()
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
$workdir = $this->workdir();
|
||||
if (str($workdir)->endsWith($this->uuid)) {
|
||||
instant_remote_process(["rm -rf " . $this->workdir()], $server, false);
|
||||
}
|
||||
}
|
||||
|
||||
public function realStatus()
|
||||
{
|
||||
return $this->getRawOriginal('status');
|
||||
}
|
||||
public function status(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: function ($value) {
|
||||
if (str($value)->contains('(')) {
|
||||
$status = str($value)->before('(')->trim()->value();
|
||||
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||
} else if (str($value)->contains(':')) {
|
||||
$status = str($value)->before(':')->trim()->value();
|
||||
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||
} else {
|
||||
$status = $value;
|
||||
$health = 'unhealthy';
|
||||
}
|
||||
return "$status:$health";
|
||||
},
|
||||
get: function ($value) {
|
||||
if (str($value)->contains('(')) {
|
||||
$status = str($value)->before('(')->trim()->value();
|
||||
$health = str($value)->after('(')->before(')')->trim()->value() ?? 'unhealthy';
|
||||
} else if (str($value)->contains(':')) {
|
||||
$status = str($value)->before(':')->trim()->value();
|
||||
$health = str($value)->after(':')->trim()->value() ?? 'unhealthy';
|
||||
} else {
|
||||
$status = $value;
|
||||
$health = 'unhealthy';
|
||||
}
|
||||
return "$status:$health";
|
||||
},
|
||||
);
|
||||
}
|
||||
public function tags()
|
||||
{
|
||||
return $this->morphToMany(Tag::class, 'taggable');
|
||||
}
|
||||
public function project()
|
||||
{
|
||||
return data_get($this, 'environment.project');
|
||||
}
|
||||
public function team()
|
||||
{
|
||||
return data_get($this, 'environment.project.team');
|
||||
}
|
||||
public function link()
|
||||
{
|
||||
if (data_get($this, 'environment.project.uuid')) {
|
||||
return route('project.database.configuration', [
|
||||
'project_uuid' => data_get($this, 'environment.project.uuid'),
|
||||
'environment_name' => data_get($this, 'environment.name'),
|
||||
'database_uuid' => data_get($this, 'uuid')
|
||||
]);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
public function isLogDrainEnabled()
|
||||
{
|
||||
return data_get($this, 'is_log_drain_enabled', false);
|
||||
}
|
||||
|
||||
public function portsMappings(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => $value === "" ? null : $value,
|
||||
);
|
||||
}
|
||||
|
||||
public function portsMappingsArray(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => is_null($this->ports_mappings)
|
||||
? []
|
||||
: explode(',', $this->ports_mappings),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
public function type(): string
|
||||
{
|
||||
return 'standalone-keydb';
|
||||
}
|
||||
public function get_db_url(bool $useInternal = false): string
|
||||
{
|
||||
if ($this->is_public && !$useInternal) {
|
||||
return "redis://{$this->keydb_password}@{$this->destination->server->getIp}:{$this->public_port}/0";
|
||||
} else {
|
||||
return "redis://{$this->keydb_password}@{$this->uuid}:6379/0";
|
||||
}
|
||||
}
|
||||
|
||||
public function environment()
|
||||
{
|
||||
return $this->belongsTo(Environment::class);
|
||||
}
|
||||
|
||||
public function fileStorages()
|
||||
{
|
||||
return $this->morphMany(LocalFileVolume::class, 'resource');
|
||||
}
|
||||
|
||||
public function destination()
|
||||
{
|
||||
return $this->morphTo();
|
||||
}
|
||||
|
||||
public function environment_variables(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class);
|
||||
}
|
||||
|
||||
public function runtime_environment_variables(): HasMany
|
||||
{
|
||||
return $this->hasMany(EnvironmentVariable::class);
|
||||
}
|
||||
|
||||
public function persistentStorages()
|
||||
{
|
||||
return $this->morphMany(LocalPersistentVolume::class, 'resource');
|
||||
}
|
||||
|
||||
public function scheduledBackups()
|
||||
{
|
||||
return $this->morphMany(ScheduledDatabaseBackup::class, 'database');
|
||||
}
|
||||
|
||||
}
|
@ -43,6 +43,45 @@ protected static function booted()
|
||||
$database->tags()->detach();
|
||||
});
|
||||
}
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->image . $this->ports_mappings . $this->mariadb_conf;
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
|
||||
$newConfigHash = md5($newConfigHash);
|
||||
$oldConfigHash = data_get($this, 'config_hash');
|
||||
if ($oldConfigHash === null) {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if ($oldConfigHash === $newConfigHash) {
|
||||
return false;
|
||||
} else {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
}
|
||||
public function workdir()
|
||||
{
|
||||
return database_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
public function delete_configurations()
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
$workdir = $this->workdir();
|
||||
if (str($workdir)->endsWith($this->uuid)) {
|
||||
instant_remote_process(["rm -rf " . $this->workdir()], $server, false);
|
||||
}
|
||||
}
|
||||
public function realStatus()
|
||||
{
|
||||
return $this->getRawOriginal('status');
|
||||
|
@ -46,6 +46,45 @@ protected static function booted()
|
||||
$database->tags()->detach();
|
||||
});
|
||||
}
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->image . $this->ports_mappings . $this->mongo_conf;
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
|
||||
$newConfigHash = md5($newConfigHash);
|
||||
$oldConfigHash = data_get($this, 'config_hash');
|
||||
if ($oldConfigHash === null) {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if ($oldConfigHash === $newConfigHash) {
|
||||
return false;
|
||||
} else {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
}
|
||||
public function workdir()
|
||||
{
|
||||
return database_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
public function delete_configurations()
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
$workdir = $this->workdir();
|
||||
if (str($workdir)->endsWith($this->uuid)) {
|
||||
instant_remote_process(["rm -rf " . $this->workdir()], $server, false);
|
||||
}
|
||||
}
|
||||
public function realStatus()
|
||||
{
|
||||
return $this->getRawOriginal('status');
|
||||
|
@ -43,6 +43,45 @@ protected static function booted()
|
||||
$database->tags()->detach();
|
||||
});
|
||||
}
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->image . $this->ports_mappings . $this->mysql_conf;
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
|
||||
$newConfigHash = md5($newConfigHash);
|
||||
$oldConfigHash = data_get($this, 'config_hash');
|
||||
if ($oldConfigHash === null) {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if ($oldConfigHash === $newConfigHash) {
|
||||
return false;
|
||||
} else {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
}
|
||||
public function workdir()
|
||||
{
|
||||
return database_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
public function delete_configurations()
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
$workdir = $this->workdir();
|
||||
if (str($workdir)->endsWith($this->uuid)) {
|
||||
instant_remote_process(["rm -rf " . $this->workdir()], $server, false);
|
||||
}
|
||||
}
|
||||
public function realStatus()
|
||||
{
|
||||
return $this->getRawOriginal('status');
|
||||
|
@ -43,6 +43,45 @@ protected static function booted()
|
||||
$database->tags()->detach();
|
||||
});
|
||||
}
|
||||
public function workdir()
|
||||
{
|
||||
return database_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
public function delete_configurations()
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
$workdir = $this->workdir();
|
||||
if (str($workdir)->endsWith($this->uuid)) {
|
||||
instant_remote_process(["rm -rf " . $this->workdir()], $server, false);
|
||||
}
|
||||
}
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->image . $this->ports_mappings . $this->postgres_initdb_args . $this->postgres_host_auth_method;
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
|
||||
$newConfigHash = md5($newConfigHash);
|
||||
$oldConfigHash = data_get($this, 'config_hash');
|
||||
if ($oldConfigHash === null) {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if ($oldConfigHash === $newConfigHash) {
|
||||
return false;
|
||||
} else {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
}
|
||||
public function realStatus()
|
||||
{
|
||||
return $this->getRawOriginal('status');
|
||||
|
@ -38,6 +38,45 @@ protected static function booted()
|
||||
$database->tags()->detach();
|
||||
});
|
||||
}
|
||||
public function isConfigurationChanged(bool $save = false)
|
||||
{
|
||||
$newConfigHash = $this->image . $this->ports_mappings . $this->redis_conf;
|
||||
$newConfigHash .= json_encode($this->environment_variables()->get('updated_at'));
|
||||
$newConfigHash = md5($newConfigHash);
|
||||
$oldConfigHash = data_get($this, 'config_hash');
|
||||
if ($oldConfigHash === null) {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if ($oldConfigHash === $newConfigHash) {
|
||||
return false;
|
||||
} else {
|
||||
if ($save) {
|
||||
$this->config_hash = $newConfigHash;
|
||||
$this->save();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
public function isExited()
|
||||
{
|
||||
return (bool) str($this->status)->startsWith('exited');
|
||||
}
|
||||
public function workdir()
|
||||
{
|
||||
return database_configuration_dir() . "/{$this->uuid}";
|
||||
}
|
||||
public function delete_configurations()
|
||||
{
|
||||
$server = data_get($this, 'destination.server');
|
||||
$workdir = $this->workdir();
|
||||
if (str($workdir)->endsWith($this->uuid)) {
|
||||
instant_remote_process(["rm -rf " . $this->workdir()], $server, false);
|
||||
}
|
||||
}
|
||||
public function realStatus()
|
||||
{
|
||||
return $this->getRawOriginal('status');
|
||||
|
@ -20,6 +20,18 @@ public function redis()
|
||||
{
|
||||
return $this->morphMany(StandaloneRedis::class, 'destination');
|
||||
}
|
||||
public function keydbs()
|
||||
{
|
||||
return $this->morphMany(StandaloneKeydb::class, 'destination');
|
||||
}
|
||||
public function dragonflies()
|
||||
{
|
||||
return $this->morphMany(StandaloneDragonfly::class, 'destination');
|
||||
}
|
||||
public function clickhouses()
|
||||
{
|
||||
return $this->morphMany(StandaloneClickhouse::class, 'destination');
|
||||
}
|
||||
public function mongodbs()
|
||||
{
|
||||
return $this->morphMany(StandaloneMongodb::class, 'destination');
|
||||
@ -50,7 +62,10 @@ public function databases()
|
||||
$mongodbs = $this->mongodbs;
|
||||
$mysqls = $this->mysqls;
|
||||
$mariadbs = $this->mariadbs;
|
||||
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs);
|
||||
$keydbs = $this->keydbs;
|
||||
$dragonflies = $this->dragonflies;
|
||||
$clickhouses = $this->clickhouses;
|
||||
return $postgresqls->concat($redis)->concat($mongodbs)->concat($mysqls)->concat($mariadbs)->concat($keydbs)->concat($dragonflies)->concat($clickhouses);
|
||||
}
|
||||
|
||||
public function attachedTo()
|
||||
|
@ -22,6 +22,7 @@ public function __construct(
|
||||
public bool $required = false,
|
||||
public bool $disabled = false,
|
||||
public bool $readonly = false,
|
||||
public bool $allowTab = false,
|
||||
public ?string $helper = null,
|
||||
public bool $realtimeValidation = false,
|
||||
public bool $allowToPeak = true,
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
const REDACTED = '<REDACTED>';
|
||||
const DATABASE_TYPES = ['postgresql', 'redis', 'mongodb', 'mysql', 'mariadb'];
|
||||
const DATABASE_TYPES = ['postgresql', 'redis', 'mongodb', 'mysql', 'mariadb', 'keydb', 'dragonfly', 'clickhouse'];
|
||||
const VALID_CRON_STRINGS = [
|
||||
'every_minute' => '* * * * *',
|
||||
'hourly' => '0 * * * *',
|
||||
|
@ -1,7 +1,10 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
@ -90,6 +93,49 @@ function create_standalone_mariadb($environment_id, $destination_uuid): Standalo
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
]);
|
||||
}
|
||||
function create_standalone_keydb($environment_id, $destination_uuid): StandaloneKeydb
|
||||
{
|
||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||
if (!$destination) {
|
||||
throw new Exception('Destination not found');
|
||||
}
|
||||
return StandaloneKeydb::create([
|
||||
'name' => generate_database_name('keydb'),
|
||||
'keydb_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
|
||||
'environment_id' => $environment_id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
]);
|
||||
}
|
||||
|
||||
function create_standalone_dragonfly($environment_id, $destination_uuid): StandaloneDragonfly
|
||||
{
|
||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||
if (!$destination) {
|
||||
throw new Exception('Destination not found');
|
||||
}
|
||||
return StandaloneDragonfly::create([
|
||||
'name' => generate_database_name('dragonfly'),
|
||||
'dragonfly_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
|
||||
'environment_id' => $environment_id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
]);
|
||||
}
|
||||
function create_standalone_clickhouse($environment_id, $destination_uuid): StandaloneClickhouse
|
||||
{
|
||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||
if (!$destination) {
|
||||
throw new Exception('Destination not found');
|
||||
}
|
||||
return StandaloneClickhouse::create([
|
||||
'name' => generate_database_name('clickhouse'),
|
||||
'clickhouse_admin_password' => \Illuminate\Support\Str::password(length: 64, symbols: false),
|
||||
'environment_id' => $environment_id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete file locally on the filesystem.
|
||||
|
@ -582,7 +582,7 @@ function validateComposeFile(string $compose, int $server_id): string|Throwable
|
||||
|
||||
function escapeEnvVariables($value)
|
||||
{
|
||||
$search = array("\\", "\r", "\t", "\x0", '"', "'", "$");
|
||||
$replace = array("\\\\", "\\r", "\\t", "\\0", '\"', "\'", "$$");
|
||||
$search = array("\\", "\r", "\t", "\x0", '"', "'");
|
||||
$replace = array("\\\\", "\\r", "\\t", "\\0", '\"', "\'");
|
||||
return str_replace($search, $replace, $value);
|
||||
}
|
||||
|
@ -11,6 +11,9 @@
|
||||
use App\Models\Service;
|
||||
use App\Models\ServiceApplication;
|
||||
use App\Models\ServiceDatabase;
|
||||
use App\Models\StandaloneClickhouse;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use App\Models\StandaloneMysql;
|
||||
@ -463,15 +466,14 @@ function getServiceTemplates()
|
||||
|
||||
function getResourceByUuid(string $uuid, ?int $teamId = null)
|
||||
{
|
||||
$resource = queryResourcesByUuid($uuid);
|
||||
if (!is_null($teamId)) {
|
||||
if (!is_null($resource) && $resource->environment->project->team_id === $teamId) {
|
||||
return $resource;
|
||||
}
|
||||
if (is_null($teamId)) {
|
||||
return null;
|
||||
} else {
|
||||
}
|
||||
$resource = queryResourcesByUuid($uuid);
|
||||
if (!is_null($resource) && $resource->environment->project->team_id === $teamId) {
|
||||
return $resource;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
function queryResourcesByUuid(string $uuid)
|
||||
{
|
||||
@ -490,6 +492,12 @@ function queryResourcesByUuid(string $uuid)
|
||||
if ($mysql) return $mysql;
|
||||
$mariadb = StandaloneMariadb::whereUuid($uuid)->first();
|
||||
if ($mariadb) return $mariadb;
|
||||
$keydb = StandaloneKeydb::whereUuid($uuid)->first();
|
||||
if ($keydb) return $keydb;
|
||||
$dragonfly = StandaloneDragonfly::whereUuid($uuid)->first();
|
||||
if ($dragonfly) return $dragonfly;
|
||||
$clickhouse = StandaloneClickhouse::whereUuid($uuid)->first();
|
||||
if ($clickhouse) return $clickhouse;
|
||||
return $resource;
|
||||
}
|
||||
function generatTagDeployWebhook($tag_name)
|
||||
|
@ -27,6 +27,7 @@
|
||||
"lorisleiva/laravel-actions": "^2.7",
|
||||
"nubs/random-name-generator": "^2.2",
|
||||
"phpseclib/phpseclib": "~3.0",
|
||||
"pion/laravel-chunk-upload": "^1.5",
|
||||
"poliander/cron": "^3.0",
|
||||
"purplepixie/phpdns": "^2.1",
|
||||
"pusher/pusher-php-server": "^7.2",
|
||||
|
68
composer.lock
generated
68
composer.lock
generated
@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "e095b8a9eb22df2943cbc3e9649ff9e8",
|
||||
"content-hash": "e6fd1d5c5183226a78df717b52343393",
|
||||
"packages": [
|
||||
{
|
||||
"name": "amphp/amp",
|
||||
@ -6370,6 +6370,72 @@
|
||||
},
|
||||
"time": "2021-10-28T11:13:42+00:00"
|
||||
},
|
||||
{
|
||||
"name": "pion/laravel-chunk-upload",
|
||||
"version": "v1.5.4",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/pionl/laravel-chunk-upload.git",
|
||||
"reference": "cfbc4292ddcace51308a4f2f446d310aa04e6133"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/pionl/laravel-chunk-upload/zipball/cfbc4292ddcace51308a4f2f446d310aa04e6133",
|
||||
"reference": "cfbc4292ddcace51308a4f2f446d310aa04e6133",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"illuminate/console": "5.2 - 5.8 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0",
|
||||
"illuminate/filesystem": "5.2 - 5.8 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0",
|
||||
"illuminate/http": "5.2 - 5.8 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0",
|
||||
"illuminate/support": "5.2 - 5.8 | ^6.0 | ^7.0 | ^8.0 | ^9.0 | ^10.0 | ^11.0"
|
||||
},
|
||||
"require-dev": {
|
||||
"friendsofphp/php-cs-fixer": "^2.16.0 | ^3.52.0",
|
||||
"mockery/mockery": "^1.1.0 | ^1.3.0 | ^1.6.0",
|
||||
"overtrue/phplint": "^1.1 | ^2.0 | ^9.1",
|
||||
"phpunit/phpunit": "5.7 | 6.0 | 7.0 | 7.5 | 8.4 | ^8.5 | ^9.3 | ^10.0 | ^11.0"
|
||||
},
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"laravel": {
|
||||
"providers": [
|
||||
"Pion\\Laravel\\ChunkUpload\\Providers\\ChunkUploadServiceProvider"
|
||||
]
|
||||
}
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Pion\\Laravel\\ChunkUpload\\": "src/"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Martin Kluska",
|
||||
"email": "martin@kluska.cz"
|
||||
}
|
||||
],
|
||||
"description": "Service for chunked upload with several js providers",
|
||||
"support": {
|
||||
"issues": "https://github.com/pionl/laravel-chunk-upload/issues",
|
||||
"source": "https://github.com/pionl/laravel-chunk-upload/tree/v1.5.4"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://revolut.me/martinpv7n",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://github.com/pionl",
|
||||
"type": "github"
|
||||
}
|
||||
],
|
||||
"time": "2024-03-25T15:50:07+00:00"
|
||||
},
|
||||
{
|
||||
"name": "poliander/cron",
|
||||
"version": "3.1.0",
|
||||
|
44
config/chunk-upload.php
Normal file
44
config/chunk-upload.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* @see https://github.com/pionl/laravel-chunk-upload
|
||||
*/
|
||||
|
||||
return [
|
||||
/*
|
||||
* The storage config
|
||||
*/
|
||||
'storage' => [
|
||||
/*
|
||||
* Returns the folder name of the chunks. The location is in storage/app/{folder_name}
|
||||
*/
|
||||
'chunks' => 'chunks',
|
||||
'disk' => 'local',
|
||||
],
|
||||
'clear' => [
|
||||
/*
|
||||
* How old chunks we should delete
|
||||
*/
|
||||
'timestamp' => '-1 HOURS',
|
||||
'schedule' => [
|
||||
'enabled' => false,
|
||||
'cron' => '25 * * * *', // run every hour on the 25th minute
|
||||
],
|
||||
],
|
||||
'chunk' => [
|
||||
// setup for the chunk naming setup to ensure same name upload at same time
|
||||
'name' => [
|
||||
'use' => [
|
||||
'session' => true, // should the chunk name use the session id? The uploader must send cookie!,
|
||||
'browser' => false, // instead of session we can use the ip and browser?
|
||||
],
|
||||
],
|
||||
],
|
||||
'handlers' => [
|
||||
// A list of handlers/providers that will be appended to existing list of handlers
|
||||
'custom' => [],
|
||||
// Overrides the list of handlers - use only what you really want
|
||||
'override' => [
|
||||
// \Pion\Laravel\ChunkUpload\Handler\DropZoneUploadHandler::class
|
||||
],
|
||||
],
|
||||
];
|
@ -7,7 +7,7 @@
|
||||
|
||||
// The release version of your application
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||
'release' => '4.0.0-beta.255',
|
||||
'release' => '4.0.0-beta.256',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.255';
|
||||
return '4.0.0-beta.256';
|
||||
|
@ -0,0 +1,64 @@
|
||||
<?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::create('standalone_keydbs', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->string('name');
|
||||
$table->string('description')->nullable();
|
||||
|
||||
$table->text('keydb_password');
|
||||
$table->longText('keydb_conf')->nullable();
|
||||
|
||||
$table->boolean('is_log_drain_enabled')->default(false);
|
||||
$table->boolean('is_include_timestamps')->default(false);
|
||||
$table->softDeletes();
|
||||
|
||||
$table->string('status')->default('exited');
|
||||
|
||||
$table->string('image')->default('eqalpha/keydb:latest');
|
||||
|
||||
$table->boolean('is_public')->default(false);
|
||||
$table->integer('public_port')->nullable();
|
||||
$table->text('ports_mappings')->nullable();
|
||||
|
||||
$table->string('limits_memory')->default("0");
|
||||
$table->string('limits_memory_swap')->default("0");
|
||||
$table->integer('limits_memory_swappiness')->default(60);
|
||||
$table->string('limits_memory_reservation')->default("0");
|
||||
|
||||
$table->string('limits_cpus')->default("0");
|
||||
$table->string('limits_cpuset')->nullable()->default(null);
|
||||
$table->integer('limits_cpu_shares')->default(1024);
|
||||
|
||||
$table->timestamp('started_at')->nullable();
|
||||
$table->morphs('destination');
|
||||
$table->foreignId('environment_id')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::table('environment_variables', function (Blueprint $table) {
|
||||
$table->foreignId('standalone_keydb_id')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('standalone_keydbs');
|
||||
Schema::table('environment_variables', function (Blueprint $table) {
|
||||
$table->dropColumn('standalone_keydb_id');
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,63 @@
|
||||
<?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::create('standalone_dragonflies', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->string('name');
|
||||
$table->string('description')->nullable();
|
||||
|
||||
$table->text('dragonfly_password');
|
||||
|
||||
$table->boolean('is_log_drain_enabled')->default(false);
|
||||
$table->boolean('is_include_timestamps')->default(false);
|
||||
$table->softDeletes();
|
||||
|
||||
$table->string('status')->default('exited');
|
||||
|
||||
$table->string('image')->default('docker.dragonflydb.io/dragonflydb/dragonfly');
|
||||
|
||||
$table->boolean('is_public')->default(false);
|
||||
$table->integer('public_port')->nullable();
|
||||
$table->text('ports_mappings')->nullable();
|
||||
|
||||
$table->string('limits_memory')->default("0");
|
||||
$table->string('limits_memory_swap')->default("0");
|
||||
$table->integer('limits_memory_swappiness')->default(60);
|
||||
$table->string('limits_memory_reservation')->default("0");
|
||||
|
||||
$table->string('limits_cpus')->default("0");
|
||||
$table->string('limits_cpuset')->nullable()->default(null);
|
||||
$table->integer('limits_cpu_shares')->default(1024);
|
||||
|
||||
$table->timestamp('started_at')->nullable();
|
||||
$table->morphs('destination');
|
||||
$table->foreignId('environment_id')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::table('environment_variables', function (Blueprint $table) {
|
||||
$table->foreignId('standalone_dragonfly_id')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('standalone_dragonflies');
|
||||
Schema::table('environment_variables', function (Blueprint $table) {
|
||||
$table->dropColumn('standalone_dragonfly_id');
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,64 @@
|
||||
<?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::create('standalone_clickhouses', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->string('name');
|
||||
$table->string('description')->nullable();
|
||||
|
||||
$table->string('clickhouse_admin_user')->default('default');
|
||||
$table->text('clickhouse_admin_password');
|
||||
|
||||
$table->boolean('is_log_drain_enabled')->default(false);
|
||||
$table->boolean('is_include_timestamps')->default(false);
|
||||
$table->softDeletes();
|
||||
|
||||
$table->string('status')->default('exited');
|
||||
|
||||
$table->string('image')->default('bitnami/clickhouse');
|
||||
|
||||
$table->boolean('is_public')->default(false);
|
||||
$table->integer('public_port')->nullable();
|
||||
$table->text('ports_mappings')->nullable();
|
||||
|
||||
$table->string('limits_memory')->default("0");
|
||||
$table->string('limits_memory_swap')->default("0");
|
||||
$table->integer('limits_memory_swappiness')->default(60);
|
||||
$table->string('limits_memory_reservation')->default("0");
|
||||
|
||||
$table->string('limits_cpus')->default("0");
|
||||
$table->string('limits_cpuset')->nullable()->default(null);
|
||||
$table->integer('limits_cpu_shares')->default(1024);
|
||||
|
||||
$table->timestamp('started_at')->nullable();
|
||||
$table->morphs('destination');
|
||||
$table->foreignId('environment_id')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
Schema::table('environment_variables', function (Blueprint $table) {
|
||||
$table->foreignId('standalone_clickhouse_id')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('standalone_clickhouses');
|
||||
Schema::table('environment_variables', function (Blueprint $table) {
|
||||
$table->dropColumn('standalone_clickhouse_id');
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,30 @@
|
||||
<?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->string('chown')->nullable();
|
||||
$table->string('chmod')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('local_file_volumes', function (Blueprint $table) {
|
||||
$table->dropColumn('chown');
|
||||
$table->dropColumn('chmod');
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,76 @@
|
||||
<?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->string('config_hash')->nullable();
|
||||
});
|
||||
Schema::table('standalone_redis', function (Blueprint $table) {
|
||||
$table->string('config_hash')->nullable();
|
||||
});
|
||||
Schema::table('standalone_mysqls', function (Blueprint $table) {
|
||||
$table->string('config_hash')->nullable();
|
||||
});
|
||||
Schema::table('standalone_mariadbs', function (Blueprint $table) {
|
||||
$table->string('config_hash')->nullable();
|
||||
});
|
||||
Schema::table('standalone_mongodbs', function (Blueprint $table) {
|
||||
$table->string('config_hash')->nullable();
|
||||
});
|
||||
Schema::table('standalone_keydbs', function (Blueprint $table) {
|
||||
$table->string('config_hash')->nullable();
|
||||
});
|
||||
Schema::table('standalone_dragonflies', function (Blueprint $table) {
|
||||
$table->string('config_hash')->nullable();
|
||||
});
|
||||
Schema::table('standalone_clickhouses', function (Blueprint $table) {
|
||||
$table->string('config_hash')->nullable();
|
||||
});
|
||||
Schema::table('services', function (Blueprint $table) {
|
||||
$table->string('config_hash')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('standalone_postgresqls', function (Blueprint $table) {
|
||||
$table->dropColumn('config_hash');
|
||||
});
|
||||
Schema::table('standalone_redis', function (Blueprint $table) {
|
||||
$table->dropColumn('config_hash');
|
||||
});
|
||||
Schema::table('standalone_mysqls', function (Blueprint $table) {
|
||||
$table->dropColumn('config_hash');
|
||||
});
|
||||
Schema::table('standalone_mariadbs', function (Blueprint $table) {
|
||||
$table->dropColumn('config_hash');
|
||||
});
|
||||
Schema::table('standalone_mongodbs', function (Blueprint $table) {
|
||||
$table->dropColumn('config_hash');
|
||||
});
|
||||
Schema::table('standalone_keydbs', function (Blueprint $table) {
|
||||
$table->dropColumn('config_hash');
|
||||
});
|
||||
Schema::table('standalone_dragonflies', function (Blueprint $table) {
|
||||
$table->dropColumn('config_hash');
|
||||
});
|
||||
Schema::table('standalone_clickhouses', function (Blueprint $table) {
|
||||
$table->dropColumn('config_hash');
|
||||
});
|
||||
Schema::table('services', function (Blueprint $table) {
|
||||
$table->dropColumn('config_hash');
|
||||
});
|
||||
}
|
||||
};
|
@ -1,2 +1,5 @@
|
||||
#!/command/execlineb -P
|
||||
su - webuser -c "php /var/www/html/artisan horizon"
|
||||
foreground {
|
||||
s6-sleep 5
|
||||
su - webuser -c "php /var/www/html/artisan start:horizon"
|
||||
}
|
||||
|
@ -1,2 +1,5 @@
|
||||
#!/command/execlineb -P
|
||||
su - webuser -c "php /var/www/html/artisan schedule:work"
|
||||
foreground {
|
||||
s6-sleep 5
|
||||
su - webuser -c "php /var/www/html/artisan start:scheduler"
|
||||
}
|
||||
|
@ -289,3 +289,7 @@ .fullscreen {
|
||||
.toast {
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dz-button {
|
||||
@apply w-full p-4 py-10 my-4 font-bold bg-white border dark:border-coolgray-400 dark:text-white dark:bg-transparent hover:dark:bg-coolgray-400;
|
||||
}
|
||||
|
@ -1,41 +1,41 @@
|
||||
<x-layout-simple>
|
||||
<div class="min-h-screen hero">
|
||||
<div>
|
||||
<div class="flex flex-col items-center ">
|
||||
<a href="{{ route('dashboard') }}">
|
||||
<div class="text-5xl font-bold tracking-tight text-center dark:text-white">Coolify</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="flex items-center justify-center pb-4 text-center">
|
||||
<section class="bg-gray-50 dark:bg-base">
|
||||
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
|
||||
<a class="flex items-center text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
|
||||
Coolify
|
||||
</a>
|
||||
<div class="flex items-center justify-center pb-6 text-center">
|
||||
{{ __('auth.reset_password') }}
|
||||
</div>
|
||||
<div>
|
||||
<form action="/reset-password" method="POST" class="flex flex-col gap-2">
|
||||
@csrf
|
||||
<input hidden id="token" name="token" value="{{ request()->route('token') }}">
|
||||
<input hidden value="{{ request()->query('email') }}" type="email" name="email"
|
||||
label="{{ __('input.email') }}" />
|
||||
<div class="flex flex-col gap-2">
|
||||
<x-forms.input required type="password" id="password" name="password"
|
||||
label="{{ __('input.password') }}" autofocus />
|
||||
<x-forms.input required type="password" id="password_confirmation" name="password_confirmation"
|
||||
label="{{ __('input.password.again') }}" />
|
||||
</div>
|
||||
<x-forms.button type="submit">{{ __('auth.reset_password') }}</x-forms.button>
|
||||
</form>
|
||||
@if ($errors->any())
|
||||
<div class="text-xs text-center text-error">
|
||||
@foreach ($errors->all() as $error)
|
||||
<p>{{ $error }}</p>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@if (session('status'))
|
||||
<div class="mb-4 font-medium text-green-600">
|
||||
{{ session('status') }}
|
||||
</div>
|
||||
@endif
|
||||
<div class="w-full bg-white shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base ">
|
||||
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
|
||||
<form action="/reset-password" method="POST" class="flex flex-col gap-2">
|
||||
@csrf
|
||||
<input hidden id="token" name="token" value="{{ request()->route('token') }}">
|
||||
<input hidden value="{{ request()->query('email') }}" type="email" name="email"
|
||||
label="{{ __('input.email') }}" />
|
||||
<div class="flex flex-col gap-2">
|
||||
<x-forms.input required type="password" id="password" name="password"
|
||||
label="{{ __('input.password') }}" autofocus />
|
||||
<x-forms.input required type="password" id="password_confirmation"
|
||||
name="password_confirmation" label="{{ __('input.password.again') }}" />
|
||||
</div>
|
||||
<x-forms.button type="submit">{{ __('auth.reset_password') }}</x-forms.button>
|
||||
</form>
|
||||
@if ($errors->any())
|
||||
<div class="text-xs text-center text-error">
|
||||
@foreach ($errors->all() as $error)
|
||||
<p>{{ $error }}</p>
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
@if (session('status'))
|
||||
<div class="mb-4 font-medium text-green-600">
|
||||
{{ session('status') }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</x-layout-simple>
|
||||
|
@ -5,11 +5,14 @@
|
||||
@endisset
|
||||
@isset($confirmAction)
|
||||
x-on:{{ explode('(', $confirmAction)[0] }}.window="$wire.{{ explode('(', $confirmAction)[0] }}"
|
||||
@endisset
|
||||
>
|
||||
@endisset>
|
||||
|
||||
{{ $slot }}
|
||||
@if ($attributes->whereStartsWith('wire:click')->first())
|
||||
<x-loading-on-button wire:target="{{ $attributes->whereStartsWith('wire:click')->first() }}" wire:loading.delay />
|
||||
<x-loading-on-button wire:target="{{ $attributes->whereStartsWith('wire:click')->first() }}"
|
||||
wire:loading.delay />
|
||||
@elseif($attributes->whereStartsWith('wire:target')->first())
|
||||
<x-loading-on-button wire:target="{{ $attributes->whereStartsWith('wire:target')->first() }}"
|
||||
wire:loading.delay />
|
||||
@endif
|
||||
</button>
|
||||
|
@ -1,3 +1,18 @@
|
||||
<script>
|
||||
function handleKeydown(e) {
|
||||
if (e.keyCode === 9) {
|
||||
e.preventDefault();
|
||||
|
||||
e.target.setRangeText(
|
||||
'\t',
|
||||
e.target.selectionStart,
|
||||
e.target.selectionStart,
|
||||
'end'
|
||||
);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="flex-1 form-control">
|
||||
@if ($label)
|
||||
<label class="flex items-center gap-1 mb-1 text-sm font-medium">{{ $label }}
|
||||
@ -41,7 +56,7 @@ class="absolute inset-y-0 right-0 flex items-center h-6 pt-2 pr-2 cursor-pointer
|
||||
|
||||
</div>
|
||||
@else
|
||||
<textarea placeholder="{{ $placeholder }}" {{ $attributes->merge(['class' => $defaultClass]) }}
|
||||
<textarea @keydown.tab="{{ $allowTab }} && handleKeydown" placeholder="{{ $placeholder }}" {{ $attributes->merge(['class' => $defaultClass]) }}
|
||||
@if ($realtimeValidation) wire:model.debounce.200ms="{{ $id }}"
|
||||
@else
|
||||
wire:model={{ $value ?? $id }}
|
||||
|
@ -1,9 +1,14 @@
|
||||
<div {{ $attributes->merge(['class' => "group"]) }}>
|
||||
<div {{ $attributes->merge(['class' => 'group']) }}>
|
||||
<div class="info-helper">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="w-4 h-4 stroke-current">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
@isset($icon)
|
||||
{{ $icon }}
|
||||
@else
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="w-4 h-4 stroke-current">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
@endisset
|
||||
|
||||
</div>
|
||||
<div class="info-helper-popup">
|
||||
<div class="p-4">
|
||||
|
28
resources/views/components/popup-small.blade.php
Normal file
28
resources/views/components/popup-small.blade.php
Normal file
@ -0,0 +1,28 @@
|
||||
@props(['title' => 'Default title', 'description' => 'Default Description', 'buttonText' => 'Default Button Text'])
|
||||
<div x-data="{
|
||||
bannerVisible: true,
|
||||
bannerVisibleAfter: 100
|
||||
}" x-show="bannerVisible" x-transition:enter="transition ease-out duration-100"
|
||||
x-transition:enter-start="translate-y-full" x-transition:enter-end="translate-y-0"
|
||||
x-transition:leave="transition ease-in duration-300" x-transition:leave-start="translate-y-0"
|
||||
x-transition:leave-end="translate-y-full" x-init="setTimeout(() => { bannerVisible = true }, bannerVisibleAfter);"
|
||||
class="fixed bottom-0 right-0 h-auto duration-300 ease-out px-5 pb-5 max-w-[46rem] z-[999]"
|
||||
x-cloak>
|
||||
<div
|
||||
class="flex flex-col items-center justify-between w-full h-full max-w-4xl p-6 mx-auto bg-white border shadow-lg lg:border-t dark:border-coolgray-300 dark:bg-coolgray-100 lg:p-8 lg:flex-row sm:rounded">
|
||||
<div
|
||||
class="flex flex-col items-start h-full pb-0 text-xs lg:items-center lg:flex-row lg:pr-6 lg:space-x-5 dark:text-neutral-300 ">
|
||||
@if (isset($icon))
|
||||
{{ $icon }}
|
||||
|
||||
@endif
|
||||
|
||||
<div class="pt-0">
|
||||
<h4 class="w-full mb-1 text-base font-bold leading-none -translate-y-1 lg:text-xl text-neutral-900 dark:text-white">
|
||||
{{ $title }}
|
||||
</h4>
|
||||
<p class="">{{ $description }}</span></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -9,7 +9,17 @@
|
||||
{{ str($status)->before(':')->headline() }}
|
||||
</div>
|
||||
@if (!str($status)->startsWith('Proxy') && !str($status)->contains('('))
|
||||
<div class="text-xs {{ str($status)->contains('unhealthy') ? 'dark:text-warning' : 'text-success' }}">({{ str($status)->after(':') }})</div>
|
||||
@if (str($status)->contains('unhealthy'))
|
||||
<x-helper helper="Unhealthy state. <span class='dark:text-warning text-coollabs'>This doesn't mean that the resource is malfunctioning.</span><br><br>- If the resource is accessible, it indicates that no health check is configured - it is not mandatory.<br>- If the resource is not accessible (returning 404 or 503), it may indicate that a health check is needed and has not passed. <span class='dark:text-warning text-coollabs'>Your action is required.</span>" >
|
||||
<x-slot:icon>
|
||||
<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>
|
||||
</x-slot:icon>
|
||||
</x-helper>
|
||||
{{-- @else
|
||||
<div class="text-xs dark:text-success">({{ str($status)->after(':') }})</div> --}}
|
||||
@endif
|
||||
@endif
|
||||
</span>
|
||||
</div>
|
||||
|
@ -1,17 +1,19 @@
|
||||
<div class="min-h-screen hero">
|
||||
<div class="w-96 min-w-fit">
|
||||
<div class="flex flex-col items-center">
|
||||
<a href="{{ route('dashboard') }}">
|
||||
<div class="text-5xl font-bold tracking-tight text-center dark:text-white">Coolify</div>
|
||||
</a>
|
||||
<section class="bg-gray-50 dark:bg-base">
|
||||
<div class="flex flex-col items-center justify-center px-6 py-8 mx-auto md:h-screen lg:py-0">
|
||||
<a class="flex items-center mb-6 text-5xl font-extrabold tracking-tight text-gray-900 dark:text-white">
|
||||
Coolify
|
||||
</a>
|
||||
<div class="w-full bg-white shadow md:mt-0 sm:max-w-md xl:p-0 dark:bg-base ">
|
||||
<div class="p-6 space-y-4 md:space-y-6 sm:p-8">
|
||||
<form class="flex flex-col gap-2" wire:submit='submit'>
|
||||
<x-forms.input id="email" type="email" placeholder="Email" readonly label="Email" />
|
||||
<x-forms.input id="password" type="password" placeholder="New Password" label="New Password"
|
||||
required />
|
||||
<x-forms.input id="password_confirmation" type="password" placeholder="Confirm New Password"
|
||||
label="Confirm New Password" required />
|
||||
<x-forms.button type="submit">Reset Password</x-forms.button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form class="flex flex-col gap-2" wire:submit='submit'>
|
||||
<x-forms.input id="email" type="email" placeholder="Email" readonly label="Email" />
|
||||
<x-forms.input id="password" type="password" placeholder="New Password" label="New Password" required />
|
||||
<x-forms.input id="password_confirmation" type="password" placeholder="Confirm New Password"
|
||||
label="Confirm New Password" required />
|
||||
<x-forms.button type="submit">Reset Password</x-forms.button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<div>
|
||||
<h1>Configuration</h1>
|
||||
<livewire:project.shared.configuration-checker :resource="$application" />
|
||||
<livewire:project.application.heading :application="$application" />
|
||||
<div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex h-full pt-6">
|
||||
<div class="flex flex-col gap-2 xl:w-48">
|
||||
@ -27,7 +28,7 @@
|
||||
<a class="menu-item" :class="activeTab === 'source' && 'menu-item-active'"
|
||||
@click.prevent="activeTab = 'source'; window.location.hash = 'source'" href="#">Source</a>
|
||||
@endif
|
||||
<a class="menu-item" :class="activeTab === 'servers' && 'menu-item-active'" class="flex items-center gap-2"
|
||||
<a class="menu-item" :class="activeTab === 'servers' && 'menu-item-active'" class="flex items-center gap-2"
|
||||
@click.prevent="activeTab = 'servers'; window.location.hash = 'servers'" href="#">Servers
|
||||
@if (str($application->status)->contains('degraded'))
|
||||
<span title="Some servers are unavailable">
|
||||
|
@ -1,5 +1,6 @@
|
||||
<div>
|
||||
<h1>Deployments</h1>
|
||||
<livewire:project.shared.configuration-checker :resource="$application" />
|
||||
<livewire:project.application.heading :application="$application" />
|
||||
{{-- <livewire:project.application.deployment.show :application="$application" :deployments="$deployments" :deployments_count="$deployments_count" /> --}}
|
||||
<div class="flex flex-col gap-2 pb-10"
|
||||
|
@ -1,5 +1,6 @@
|
||||
<div>
|
||||
<h1 class="py-0">Deployment</h1>
|
||||
<livewire:project.shared.configuration-checker :resource="$application" />
|
||||
<livewire:project.application.heading :application="$application" />
|
||||
<div class="pt-4" x-data="{
|
||||
fullscreen: false,
|
||||
@ -85,10 +86,10 @@ class="fixed top-4 right-16" x-on:click="toggleScroll"><svg class="icon" viewBox
|
||||
@if (decode_remote_command_output($application_deployment_queue)->count() > 0)
|
||||
@foreach (decode_remote_command_output($application_deployment_queue) as $line)
|
||||
<span @class([
|
||||
'dark:text-warning' => $line['hidden'],
|
||||
'text-red-500 font-bold' => $line['type'] == 'stderr',
|
||||
'dark:text-warning whitespace-pre-line' => $line['hidden'],
|
||||
'text-red-500 font-bold whitespace-pre-line' => $line['type'] == 'stderr',
|
||||
])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
|
||||
<br>COMMAND: {{ $line['command'] }}<br>OUTPUT :
|
||||
<br><br>COMMAND: {{ $line['command'] }}<br><br>OUTPUT :
|
||||
@endif @if (str($line['output'])->contains('http://') || str($line['output'])->contains('https://'))
|
||||
@php
|
||||
$line['output'] = preg_replace(
|
||||
@ -100,6 +101,7 @@ class="fixed top-4 right-16" x-on:click="toggleScroll"><svg class="icon" viewBox
|
||||
@else
|
||||
{{ $line['output'] }}
|
||||
@endif
|
||||
<br>
|
||||
</span>
|
||||
@endforeach
|
||||
@else
|
||||
|
@ -1,30 +1,23 @@
|
||||
<div>
|
||||
<form wire:submit='submit' class="flex flex-col">
|
||||
<div x-data="{ initLoadingCompose: $wire.entangle('initLoadingCompose') }">
|
||||
<form wire:submit='submit' class="flex flex-col pb-32">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>General</h2>
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
@if ($isConfigurationChanged && !is_null($application->config_hash) && !$application->isExited())
|
||||
<div title="Configuration not applied to the running application. You need to redeploy.">
|
||||
<svg class="w-6 h-6 dark:text-warning" 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" />
|
||||
</svg>
|
||||
</div>
|
||||
@endif
|
||||
</x-forms.button>
|
||||
</div>
|
||||
<div>General configuration for your application.</div>
|
||||
<div class="flex flex-col gap-2 py-4">
|
||||
<div class="flex flex-col items-end gap-2 xl:flex-row">
|
||||
<x-forms.input id="application.name" label="Name" required />
|
||||
<x-forms.input id="application.description" label="Description" />
|
||||
<x-forms.input x-bind:disabled="initLoadingCompose" id="application.name" label="Name" required />
|
||||
<x-forms.input x-bind:disabled="initLoadingCompose" id="application.description" label="Description" />
|
||||
</div>
|
||||
|
||||
@if (!$application->dockerfile && $application->build_pack !== 'dockerimage')
|
||||
<div class="flex flex-col gap-2">
|
||||
<div class="flex gap-2">
|
||||
<x-forms.select wire:model.live="application.build_pack" label="Build Pack" required>
|
||||
<x-forms.select x-bind:disabled="initLoadingCompose" wire:model.live="application.build_pack"
|
||||
label="Build Pack" required>
|
||||
<option value="nixpacks">Nixpacks</option>
|
||||
<option value="static">Static</option>
|
||||
<option value="dockerfile">Dockerfile</option>
|
||||
@ -152,23 +145,24 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
|
||||
@endif
|
||||
@endif
|
||||
@if ($application->build_pack === 'dockercompose')
|
||||
<div class="flex flex-col gap-2" wire:init='loadComposeFile(true)'>
|
||||
<div class="flex flex-col gap-2" x-init="$wire.dispatch('loadCompose', true)">
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input placeholder="/" id="application.base_directory" label="Base Directory"
|
||||
<x-forms.input x-bind:disabled="initLoadingCompose" placeholder="/"
|
||||
id="application.base_directory" label="Base Directory"
|
||||
helper="Directory to use as root. Useful for monorepos." />
|
||||
<x-forms.input placeholder="/docker-compose.yaml" id="application.docker_compose_location"
|
||||
label="Docker Compose Location"
|
||||
<x-forms.input x-bind:disabled="initLoadingCompose" placeholder="/docker-compose.yaml"
|
||||
id="application.docker_compose_location" label="Docker Compose Location"
|
||||
helper="It is calculated together with the Base Directory:<br><span class='dark:text-warning'>{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }}</span>" />
|
||||
</div>
|
||||
<div class="pt-4">The following commands are for advanced use cases. Only modify them if you
|
||||
know what are
|
||||
you doing.</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input placeholder="docker compose build"
|
||||
<x-forms.input placeholder="docker compose build" x-bind:disabled="initLoadingCompose"
|
||||
id="application.docker_compose_custom_build_command"
|
||||
helper="If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} build</span>"
|
||||
label="Custom Build Command" />
|
||||
<x-forms.input placeholder="docker compose up -d"
|
||||
<x-forms.input placeholder="docker compose up -d" x-bind:disabled="initLoadingCompose"
|
||||
id="application.docker_compose_custom_start_command"
|
||||
helper="If you use this, you need to specify paths relatively and should use the same compose file in the custom command, otherwise the automatically configured labels / etc won't work.<br><br>So in your case, use: <span class='dark:text-warning'>docker compose -f .{{ Str::start($application->base_directory . $application->docker_compose_location, '/') }} up -d</span>"
|
||||
label="Custom Start Command" />
|
||||
@ -220,7 +214,8 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
|
||||
id="application.custom_docker_run_options" label="Custom Docker Options" />
|
||||
@endif
|
||||
@if ($application->build_pack === 'dockercompose')
|
||||
<x-forms.button wire:click="loadComposeFile">Reload Compose File</x-forms.button>
|
||||
<x-forms.button wire:target='initLoadingCompose'
|
||||
x-on:click="$wire.dispatch('loadCompose', false)">Reload Compose File</x-forms.button>
|
||||
@if ($application->settings->is_raw_compose_deployment_enabled)
|
||||
<x-forms.textarea rows="10" readonly id="application.docker_compose_raw"
|
||||
label="Docker Compose Content (applicationId: {{ $application->id }})"
|
||||
@ -257,18 +252,29 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
|
||||
|
||||
<h3 class="pt-8">Pre/Post Deployment Commands</h3>
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
<x-forms.input id="application.pre_deployment_command" label="Pre-deployment Command"
|
||||
<x-forms.input x-bind:disabled="initLoadingCompose" id="application.pre_deployment_command"
|
||||
label="Pre-deployment Command"
|
||||
helper="An optional script or command to execute in the existing container before the deployment begins." />
|
||||
<x-forms.input id="application.pre_deployment_command_container" label="Container Name"
|
||||
<x-forms.input x-bind:disabled="initLoadingCompose" id="application.pre_deployment_command_container"
|
||||
label="Container Name"
|
||||
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2 xl:flex-row">
|
||||
<x-forms.input placeholder="php artisan migrate" id="application.post_deployment_command"
|
||||
label="Post-deployment Command"
|
||||
<x-forms.input x-bind:disabled="initLoadingCompose" placeholder="php artisan migrate"
|
||||
id="application.post_deployment_command" label="Post-deployment Command"
|
||||
helper="An optional script or command to execute in the newly built container after the deployment completes." />
|
||||
<x-forms.input id="application.post_deployment_command_container" label="Container Name"
|
||||
<x-forms.input x-bind:disabled="initLoadingCompose" id="application.post_deployment_command_container"
|
||||
label="Container Name"
|
||||
helper="The name of the container to execute within. You can leave it blank if your application only has one container." />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
@script
|
||||
<script>
|
||||
$wire.$on('loadCompose', (isInit = true) => {
|
||||
$wire.initLoadingCompose = true;
|
||||
$wire.loadComposeFile(isInit);
|
||||
});
|
||||
</script>
|
||||
@endscript
|
||||
</div>
|
||||
|
@ -24,7 +24,7 @@ class="relative flex flex-col p-4 bg-white box-without-bg dark:bg-coolgray-100"
|
||||
<div class="flex-1"></div>
|
||||
@if (data_get($execution, 'status') === 'success')
|
||||
<x-forms.button class=" dark:hover:bg-coolgray-400"
|
||||
wire:click="download({{ data_get($execution, 'id') }})">Download</x-forms.button>
|
||||
x-on:click="download_file('{{ data_get($execution, 'id') }}')">Download</x-forms.button>
|
||||
@endif
|
||||
<x-modal-confirmation isErrorButton action="deleteBackup({{ data_get($execution, 'id') }})">
|
||||
<x-slot:button-title>
|
||||
@ -34,7 +34,13 @@ class="relative flex flex-col p-4 bg-white box-without-bg dark:bg-coolgray-100"
|
||||
</x-modal-confirmation>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@empty
|
||||
<div>No executions found.</div>
|
||||
@endforelse
|
||||
<script>
|
||||
function download_file(executionId) {
|
||||
window.open('/download/backup/' + executionId, '_blank');
|
||||
}
|
||||
</script>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
<div>
|
||||
<h1>Backups</h1>
|
||||
<livewire:project.shared.configuration-checker :resource="$database" />
|
||||
<livewire:project.database.heading :database="$database" />
|
||||
<div class="pt-6">
|
||||
<livewire:project.database.backup-edit :backup="$backup" :s3s="$s3s" :status="data_get($database, 'status')" />
|
||||
|
@ -1,5 +1,6 @@
|
||||
<div>
|
||||
<h1>Backups</h1>
|
||||
<livewire:project.shared.configuration-checker :resource="$database" />
|
||||
<livewire:project.database.heading :database="$database" />
|
||||
<div class="pt-6">
|
||||
<div class="flex gap-2 ">
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user