commit
6e6f39dc1f
@ -41,6 +41,9 @@ public function __invoke(Server $server, StandalonePostgresql $database)
|
||||
'networks' => [
|
||||
$this->database->destination->network,
|
||||
],
|
||||
'labels' => [
|
||||
'coolify.managed' => 'true',
|
||||
],
|
||||
'healthcheck' => [
|
||||
'test' => [
|
||||
'CMD-SHELL',
|
||||
|
160
app/Actions/Database/StartRedis.php
Normal file
160
app/Actions/Database/StartRedis.php
Normal file
@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Database;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Support\Str;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
|
||||
class StartRedis
|
||||
{
|
||||
use AsAction;
|
||||
|
||||
public StandaloneRedis $database;
|
||||
public array $commands = [];
|
||||
public string $configuration_dir;
|
||||
|
||||
|
||||
public function handle(Server $server, StandaloneRedis $database)
|
||||
{
|
||||
$this->database = $database;
|
||||
|
||||
$startCommand = "redis-server --requirepass {$this->database->redis_password} --appendonly yes";
|
||||
|
||||
$container_name = $this->database->uuid;
|
||||
$this->configuration_dir = database_configuration_dir() . '/' . $container_name;
|
||||
|
||||
$this->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_redis();
|
||||
|
||||
$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' => [
|
||||
'CMD-SHELL',
|
||||
'redis-cli',
|
||||
'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' => $this->database->limits_cpus,
|
||||
'cpuset' => $this->database->limits_cpuset,
|
||||
'cpu_shares' => $this->database->limits_cpu_shares,
|
||||
]
|
||||
],
|
||||
'networks' => [
|
||||
$this->database->destination->network => [
|
||||
'external' => true,
|
||||
'name' => $this->database->destination->network,
|
||||
'attachable' => 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->redis_conf)) {
|
||||
$docker_compose['services'][$container_name]['volumes'][] = [
|
||||
'type' => 'bind',
|
||||
'source' => $this->configuration_dir . '/redis.conf',
|
||||
'target' => '/usr/local/etc/redis/redis.conf',
|
||||
'read_only' => true,
|
||||
];
|
||||
$docker_compose['services'][$container_name]['command'] = $startCommand . ' /usr/local/etc/redis/redis.conf';
|
||||
}
|
||||
$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[] = "docker compose -f $this->configuration_dir/docker-compose.yml up -d";
|
||||
$this->commands[] = "echo '####### {$database->name} started.'";
|
||||
return remote_process($this->commands, $server);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
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->value");
|
||||
}
|
||||
|
||||
if ($environment_variables->filter(fn ($env) => Str::of($env)->contains('REDIS_PASSWORD'))->isEmpty()) {
|
||||
$environment_variables->push("REDIS_PASSWORD={$this->database->redis_password}");
|
||||
}
|
||||
|
||||
return $environment_variables->all();
|
||||
}
|
||||
private function add_custom_redis()
|
||||
{
|
||||
if (is_null($this->database->redis_conf)) {
|
||||
return;
|
||||
}
|
||||
$filename = 'redis.conf';
|
||||
$content = $this->database->redis_conf;
|
||||
$content_base64 = base64_encode($content);
|
||||
$this->commands[] = "echo '{$content_base64}' | base64 -d > $this->configuration_dir/{$filename}";
|
||||
|
||||
}
|
||||
}
|
42
app/Actions/Proxy/CheckProxy.php
Normal file
42
app/Actions/Proxy/CheckProxy.php
Normal file
@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
use Spatie\Activitylog\Models\Activity;
|
||||
|
||||
class CheckProxy
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Server $server)
|
||||
{
|
||||
if (!$server->isProxyShouldRun()) {
|
||||
throw new \Exception("Proxy should not run");
|
||||
}
|
||||
$status = getContainerStatus($server, 'coolify-proxy');
|
||||
if ($status === 'running') {
|
||||
$server->proxy->set('status', 'running');
|
||||
$server->save();
|
||||
return 'OK';
|
||||
}
|
||||
$ip = $server->ip;
|
||||
if ($server->id === 0) {
|
||||
$ip = 'host.docker.internal';
|
||||
}
|
||||
|
||||
$connection = @fsockopen($ip, '80');
|
||||
$connection = @fsockopen($ip, '443');
|
||||
$port80 = is_resource($connection) && fclose($connection);
|
||||
$port443 = is_resource($connection) && fclose($connection);
|
||||
ray($ip);
|
||||
if ($port80) {
|
||||
throw new \Exception("Port 80 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>");
|
||||
}
|
||||
if ($port443) {
|
||||
throw new \Exception("Port 443 is in use.<br>You must stop the process using this port.<br>Docs: <a target='_blank' href='https://coolify.io/docs'>https://coolify.io/docs</a> <br> Discord: <a target='_blank' href='https://coollabs.io/discord'>https://coollabs.io/discord</a>>");
|
||||
}
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@
|
||||
|
||||
namespace App\Actions\Proxy;
|
||||
|
||||
use App\Enums\ProxyTypes;
|
||||
use App\Models\Server;
|
||||
use Illuminate\Support\Str;
|
||||
use Lorisleiva\Actions\Concerns\AsAction;
|
||||
@ -11,56 +10,49 @@
|
||||
class StartProxy
|
||||
{
|
||||
use AsAction;
|
||||
public function handle(Server $server, bool $async = true): Activity|string
|
||||
public function handle(Server $server, bool $async = true): string|Activity
|
||||
{
|
||||
$commands = collect([]);
|
||||
$proxyType = $server->proxyType();
|
||||
if ($proxyType === ProxyTypes::NONE->value) {
|
||||
return 'OK';
|
||||
}
|
||||
$proxy_path = get_proxy_path();
|
||||
$configuration = CheckConfiguration::run($server);
|
||||
if (!$configuration) {
|
||||
throw new \Exception("Configuration is not synced");
|
||||
}
|
||||
SaveConfiguration::run($server, $configuration);
|
||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
try {
|
||||
CheckProxy::run($server);
|
||||
|
||||
$commands = $commands->merge([
|
||||
"apt-get update > /dev/null 2>&1 || true",
|
||||
"command -v lsof >/dev/null || echo '####### Installing lsof.'",
|
||||
"command -v lsof >/dev/null || apt install -y lsof",
|
||||
"command -v lsof >/dev/null || command -v fuser >/dev/null || apt install -y psmisc",
|
||||
"mkdir -p $proxy_path && cd $proxy_path",
|
||||
"echo '####### Creating Docker Compose file.'",
|
||||
"echo '####### Pulling docker image.'",
|
||||
'docker compose pull',
|
||||
"echo '####### Stopping existing coolify-proxy.'",
|
||||
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
||||
"command -v fuser >/dev/null || command -v lsof >/dev/null || echo '####### Could not kill existing processes listening on port 80 & 443. Please stop the process holding these ports...'",
|
||||
"command -v lsof >/dev/null && lsof -nt -i:80 | xargs -r kill -9 || true",
|
||||
"command -v lsof >/dev/null && lsof -nt -i:443 | xargs -r kill -9 || true",
|
||||
"command -v fuser >/dev/null && fuser -k 80/tcp || true",
|
||||
"command -v fuser >/dev/null && fuser -k 443/tcp || true",
|
||||
"systemctl disable nginx > /dev/null 2>&1 || true",
|
||||
"systemctl disable apache2 > /dev/null 2>&1 || true",
|
||||
"systemctl disable apache > /dev/null 2>&1 || true",
|
||||
"echo '####### Starting coolify-proxy.'",
|
||||
'docker compose up -d --remove-orphans',
|
||||
"echo '####### Proxy installed successfully.'"
|
||||
]);
|
||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||
if ($async) {
|
||||
$activity = remote_process($commands, $server);
|
||||
return $activity;
|
||||
} else {
|
||||
instant_remote_process($commands, $server);
|
||||
$server->proxy->set('status', 'running');
|
||||
$server->proxy->set('type', $proxyType);
|
||||
$proxyType = $server->proxyType();
|
||||
$commands = collect([]);
|
||||
$proxy_path = get_proxy_path();
|
||||
$configuration = CheckConfiguration::run($server);
|
||||
if (!$configuration) {
|
||||
throw new \Exception("Configuration is not synced");
|
||||
}
|
||||
SaveConfiguration::run($server, $configuration);
|
||||
$docker_compose_yml_base64 = base64_encode($configuration);
|
||||
$server->proxy->last_applied_settings = Str::of($docker_compose_yml_base64)->pipe('md5')->value;
|
||||
$server->save();
|
||||
return 'OK';
|
||||
$commands = $commands->merge([
|
||||
"mkdir -p $proxy_path && cd $proxy_path",
|
||||
"echo 'Creating required Docker Compose file.'",
|
||||
"echo 'Pulling docker image.'",
|
||||
'docker compose pull',
|
||||
"echo 'Stopping existing coolify-proxy.'",
|
||||
"docker compose down -v --remove-orphans > /dev/null 2>&1",
|
||||
"echo 'Starting coolify-proxy.'",
|
||||
'docker compose up -d --remove-orphans',
|
||||
"echo 'Proxy started successfully.'"
|
||||
]);
|
||||
$commands = $commands->merge(connectProxyToNetworks($server));
|
||||
if ($async) {
|
||||
$activity = remote_process($commands, $server);
|
||||
return $activity;
|
||||
} else {
|
||||
instant_remote_process($commands, $server);
|
||||
$server->proxy->set('status', 'running');
|
||||
$server->proxy->set('type', $proxyType);
|
||||
$server->save();
|
||||
return 'OK';
|
||||
}
|
||||
} catch(\Throwable $e) {
|
||||
ray($e);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -43,23 +43,24 @@ public function handle()
|
||||
$this->deleteDatabase();
|
||||
} elseif ($resource === 'Service') {
|
||||
$this->deleteService();
|
||||
} elseif($resource === 'Server') {
|
||||
} elseif ($resource === 'Server') {
|
||||
$this->deleteServer();
|
||||
}
|
||||
}
|
||||
private function deleteServer() {
|
||||
private function deleteServer()
|
||||
{
|
||||
$servers = Server::all();
|
||||
if ($servers->count() === 0) {
|
||||
$this->error('There are no applications to delete.');
|
||||
return;
|
||||
}
|
||||
$serversToDelete = multiselect(
|
||||
'What server do you want to delete?',
|
||||
$servers->pluck('id')->sort()->toArray(),
|
||||
label: 'What server do you want to delete?',
|
||||
options: $servers->pluck('name', 'id')->sortKeys(),
|
||||
);
|
||||
|
||||
foreach ($serversToDelete as $id) {
|
||||
$toDelete = Server::find($id);
|
||||
foreach ($serversToDelete as $server) {
|
||||
$toDelete = $servers->where('id', $server)->first();
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
@ -77,11 +78,12 @@ private function deleteApplication()
|
||||
}
|
||||
$applicationsToDelete = multiselect(
|
||||
'What application do you want to delete?',
|
||||
$applications->pluck('name')->sort()->toArray(),
|
||||
$applications->pluck('name', 'id')->sortKeys(),
|
||||
);
|
||||
|
||||
foreach ($applicationsToDelete as $application) {
|
||||
$toDelete = $applications->where('name', $application)->first();
|
||||
ray($application);
|
||||
$toDelete = $applications->where('id', $application)->first();
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources? ");
|
||||
if (!$confirmed) {
|
||||
@ -99,11 +101,11 @@ private function deleteDatabase()
|
||||
}
|
||||
$databasesToDelete = multiselect(
|
||||
'What database do you want to delete?',
|
||||
$databases->pluck('name')->sort()->toArray(),
|
||||
$databases->pluck('name', 'id')->sortKeys(),
|
||||
);
|
||||
|
||||
foreach ($databasesToDelete as $database) {
|
||||
$toDelete = $databases->where('name', $database)->first();
|
||||
$toDelete = $databases->where('id', $database)->first();
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
@ -111,7 +113,6 @@ private function deleteDatabase()
|
||||
}
|
||||
$toDelete->delete();
|
||||
}
|
||||
|
||||
}
|
||||
private function deleteService()
|
||||
{
|
||||
@ -122,11 +123,11 @@ private function deleteService()
|
||||
}
|
||||
$servicesToDelete = multiselect(
|
||||
'What service do you want to delete?',
|
||||
$services->pluck('name')->sort()->toArray(),
|
||||
$services->pluck('name', 'id')->sortKeys(),
|
||||
);
|
||||
|
||||
foreach ($servicesToDelete as $service) {
|
||||
$toDelete = $services->where('name', $service)->first();
|
||||
$toDelete = $services->where('id', $service)->first();
|
||||
$this->info($toDelete);
|
||||
$confirmed = confirm("Are you sure you want to delete all selected resources?");
|
||||
if (!$confirmed) {
|
||||
|
@ -19,7 +19,7 @@ public function configuration()
|
||||
if (!$environment) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
|
||||
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
|
||||
if (!$database) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
@ -37,7 +37,7 @@ public function executions()
|
||||
if (!$environment) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
|
||||
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
|
||||
if (!$database) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
@ -64,10 +64,18 @@ public function backups()
|
||||
if (!$environment) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
$database = $environment->databases->where('uuid', request()->route('database_uuid'))->first();
|
||||
$database = $environment->databases()->where('uuid', request()->route('database_uuid'))->first();
|
||||
if (!$database) {
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
// No backups for redis
|
||||
if ($database->getMorphClass() === 'App\Models\StandaloneRedis') {
|
||||
return redirect()->route('project.database.configuration', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'database_uuid' => $database->uuid,
|
||||
]);
|
||||
}
|
||||
return view('project.database.backups.all', [
|
||||
'database' => $database,
|
||||
's3s' => currentTeam()->s3s,
|
||||
|
@ -59,11 +59,15 @@ public function new()
|
||||
return redirect()->route('dashboard');
|
||||
}
|
||||
if (in_array($type, DATABASE_TYPES)) {
|
||||
$standalone_postgresql = create_standalone_postgresql($environment->id, $destination_uuid);
|
||||
if ($type->value() === "postgresql") {
|
||||
$database = create_standalone_postgresql($environment->id, $destination_uuid);
|
||||
} else if ($type->value() === 'redis') {
|
||||
$database = create_standalone_redis($environment->id, $destination_uuid);
|
||||
}
|
||||
return redirect()->route('project.database.configuration', [
|
||||
'project_uuid' => $project->uuid,
|
||||
'environment_name' => $environment->name,
|
||||
'database_uuid' => $standalone_postgresql->uuid,
|
||||
'database_uuid' => $database->uuid,
|
||||
]);
|
||||
}
|
||||
if ($type->startsWith('one-click-service-') && !is_null( (int)$server_id)) {
|
||||
|
@ -17,6 +17,7 @@ class BackupEdit extends Component
|
||||
'backup.number_of_backups_locally' => 'required|integer|min:1',
|
||||
'backup.save_s3' => 'required|boolean',
|
||||
'backup.s3_storage_id' => 'nullable|integer',
|
||||
'backup.databases_to_backup' => 'nullable',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'backup.enabled' => 'Enabled',
|
||||
@ -24,6 +25,7 @@ class BackupEdit extends Component
|
||||
'backup.number_of_backups_locally' => 'Number of Backups Locally',
|
||||
'backup.save_s3' => 'Save to S3',
|
||||
'backup.s3_storage_id' => 'S3 Storage',
|
||||
'backup.databases_to_backup' => 'Databases to Backup',
|
||||
];
|
||||
protected $messages = [
|
||||
'backup.s3_storage_id' => 'Select a S3 Storage',
|
||||
@ -37,7 +39,6 @@ public function mount()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function delete()
|
||||
{
|
||||
// TODO: Delete backup from server and add a confirmation modal
|
||||
@ -49,6 +50,7 @@ public function instantSave()
|
||||
{
|
||||
try {
|
||||
$this->custom_validate();
|
||||
|
||||
$this->backup->save();
|
||||
$this->backup->refresh();
|
||||
$this->emit('success', 'Backup updated successfully');
|
||||
@ -71,9 +73,11 @@ private function custom_validate()
|
||||
|
||||
public function submit()
|
||||
{
|
||||
ray($this->backup->s3_storage_id);
|
||||
try {
|
||||
$this->custom_validate();
|
||||
if ($this->backup->databases_to_backup == '' || $this->backup->databases_to_backup === null) {
|
||||
$this->backup->databases_to_backup = null;
|
||||
}
|
||||
$this->backup->save();
|
||||
$this->backup->refresh();
|
||||
$this->emit('success', 'Backup updated successfully');
|
||||
|
@ -13,6 +13,6 @@ public function backup_now()
|
||||
dispatch(new DatabaseBackupJob(
|
||||
backup: $this->backup
|
||||
));
|
||||
$this->emit('success', 'Backup queued. It will be available in a few minutes');
|
||||
$this->emit('success', 'Backup queued. It will be available in a few minutes.');
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ public function submit(): void
|
||||
$this->emit('error', 'Invalid Cron / Human expression.');
|
||||
return;
|
||||
}
|
||||
ScheduledDatabaseBackup::create([
|
||||
$payload = [
|
||||
'enabled' => true,
|
||||
'frequency' => $this->frequency,
|
||||
'save_s3' => $this->save_s3,
|
||||
@ -40,7 +40,11 @@ public function submit(): void
|
||||
'database_id' => $this->database->id,
|
||||
'database_type' => $this->database->getMorphClass(),
|
||||
'team_id' => currentTeam()->id,
|
||||
]);
|
||||
];
|
||||
if ($this->database->type() === 'standalone-postgresql') {
|
||||
$payload['databases_to_backup'] = $this->database->postgres_db;
|
||||
}
|
||||
ScheduledDatabaseBackup::create($payload);
|
||||
$this->emit('refreshScheduledBackups');
|
||||
} catch (\Throwable $e) {
|
||||
handleError($e, $this);
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace App\Http\Livewire\Project\Database;
|
||||
|
||||
use App\Actions\Database\StartPostgresql;
|
||||
use App\Actions\Database\StartRedis;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use Livewire\Component;
|
||||
|
||||
@ -26,6 +27,7 @@ public function check_status()
|
||||
{
|
||||
dispatch_sync(new ContainerStatusJob($this->database->destination->server));
|
||||
$this->database->refresh();
|
||||
$this->emit('refresh');
|
||||
}
|
||||
|
||||
public function mount()
|
||||
@ -40,7 +42,7 @@ public function stop()
|
||||
$this->database->destination->server
|
||||
);
|
||||
if ($this->database->is_public) {
|
||||
stopPostgresProxy($this->database);
|
||||
stopDatabaseProxy($this->database);
|
||||
$this->database->is_public = false;
|
||||
}
|
||||
$this->database->status = 'exited';
|
||||
@ -55,5 +57,9 @@ public function start()
|
||||
$activity = resolve(StartPostgresql::class)($this->database->destination->server, $this->database);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
}
|
||||
if ($this->database->type() === 'standalone-redis') {
|
||||
$activity = StartRedis::run($this->database->destination->server, $this->database);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -67,10 +67,10 @@ public function instantSave()
|
||||
}
|
||||
if ($this->database->is_public) {
|
||||
$this->emit('success', 'Starting TCP proxy...');
|
||||
startPostgresProxy($this->database);
|
||||
startDatabaseProxy($this->database);
|
||||
$this->emit('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
stopPostgresProxy($this->database);
|
||||
stopDatabaseProxy($this->database);
|
||||
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->getDbUrl();
|
||||
|
92
app/Http/Livewire/Project/Database/Redis/General.php
Normal file
92
app/Http/Livewire/Project/Database/Redis/General.php
Normal file
@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Livewire\Project\Database\Redis;
|
||||
|
||||
use App\Models\StandaloneRedis;
|
||||
use Exception;
|
||||
use Livewire\Component;
|
||||
|
||||
class General extends Component
|
||||
{
|
||||
protected $listeners = ['refresh'];
|
||||
|
||||
public StandaloneRedis $database;
|
||||
public string $db_url;
|
||||
|
||||
protected $rules = [
|
||||
'database.name' => 'required',
|
||||
'database.description' => 'nullable',
|
||||
'database.redis_conf' => 'nullable',
|
||||
'database.redis_password' => 'required',
|
||||
'database.image' => 'required',
|
||||
'database.ports_mappings' => 'nullable',
|
||||
'database.is_public' => 'nullable|boolean',
|
||||
'database.public_port' => 'nullable|integer',
|
||||
];
|
||||
protected $validationAttributes = [
|
||||
'database.name' => 'Name',
|
||||
'database.description' => 'Description',
|
||||
'database.redis_conf' => 'Redis Configuration',
|
||||
'database.redis_password' => 'Redis Password',
|
||||
'database.image' => 'Image',
|
||||
'database.ports_mappings' => 'Port Mapping',
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
];
|
||||
public function submit() {
|
||||
try {
|
||||
$this->validate();
|
||||
if ($this->database->redis_conf === "") {
|
||||
$this->database->redis_conf = null;
|
||||
}
|
||||
$this->database->save();
|
||||
$this->emit('success', 'Database updated successfully.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
if ($this->database->is_public && !$this->database->public_port) {
|
||||
$this->emit('error', 'Public port is required.');
|
||||
$this->database->is_public = false;
|
||||
return;
|
||||
}
|
||||
if ($this->database->is_public) {
|
||||
$this->emit('success', 'Starting TCP proxy...');
|
||||
startDatabaseProxy($this->database);
|
||||
$this->emit('success', 'Database is now publicly accessible.');
|
||||
} else {
|
||||
stopDatabaseProxy($this->database);
|
||||
$this->emit('success', 'Database is no longer publicly accessible.');
|
||||
}
|
||||
$this->getDbUrl();
|
||||
$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 mount()
|
||||
{
|
||||
$this->getDbUrl();
|
||||
}
|
||||
public function getDbUrl() {
|
||||
|
||||
if ($this->database->is_public) {
|
||||
$this->db_url = "redis://{$this->database->redis_password}@{$this->database->destination->server->getIp}:{$this->database->public_port}/0";
|
||||
} else {
|
||||
$this->db_url = "redis://{$this->database->redis_password}@{$this->database->uuid}:5432/0";
|
||||
}
|
||||
}
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.project.database.redis.general');
|
||||
}
|
||||
}
|
@ -75,6 +75,9 @@ public function saveVariables($isPreview)
|
||||
case 'standalone-postgresql':
|
||||
$environment->standalone_postgresql_id = $this->resource->id;
|
||||
break;
|
||||
case 'standalone-redis':
|
||||
$environment->standalone_redis_id = $this->resource->id;
|
||||
break;
|
||||
case 'service':
|
||||
$environment->service_id = $this->resource->id;
|
||||
break;
|
||||
|
@ -6,12 +6,13 @@
|
||||
use App\Models\Server;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Livewire\Component;
|
||||
|
||||
class Logs extends Component
|
||||
{
|
||||
public ?string $type = null;
|
||||
public Application|StandalonePostgresql|Service $resource;
|
||||
public Application|StandalonePostgresql|Service|StandaloneRedis $resource;
|
||||
public Server $server;
|
||||
public ?string $container = null;
|
||||
public $parameters;
|
||||
@ -33,7 +34,14 @@ public function mount()
|
||||
}
|
||||
} else if (data_get($this->parameters, 'database_uuid')) {
|
||||
$this->type = 'database';
|
||||
$this->resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->firstOrFail();
|
||||
$resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
$resource = StandaloneRedis::where('uuid', $this->parameters['database_uuid'])->first();
|
||||
if (is_null($resource)) {
|
||||
abort(404);
|
||||
}
|
||||
}
|
||||
$this->resource = $resource;
|
||||
$this->status = $this->resource->status;
|
||||
$this->server = $this->resource->destination->server;
|
||||
$this->container = $this->resource->uuid;
|
||||
|
@ -21,7 +21,7 @@ class Form extends Component
|
||||
protected $rules = [
|
||||
'server.name' => 'required|min:6',
|
||||
'server.description' => 'nullable',
|
||||
'server.ip' => 'required|ip',
|
||||
'server.ip' => 'required',
|
||||
'server.user' => 'required',
|
||||
'server.port' => 'required',
|
||||
'server.settings.is_cloudflare_tunnel' => 'required',
|
||||
@ -45,7 +45,8 @@ public function mount()
|
||||
$this->wildcard_domain = $this->server->settings->wildcard_domain;
|
||||
$this->cleanup_after_percentage = $this->server->settings->cleanup_after_percentage;
|
||||
}
|
||||
public function serverRefresh() {
|
||||
public function serverRefresh()
|
||||
{
|
||||
$this->validateServer();
|
||||
}
|
||||
public function instantSave()
|
||||
@ -61,7 +62,8 @@ public function installDocker()
|
||||
$activity = InstallDocker::run($this->server);
|
||||
$this->emit('newMonitorActivity', $activity->id);
|
||||
}
|
||||
public function checkLocalhostConnection() {
|
||||
public function checkLocalhostConnection()
|
||||
{
|
||||
$uptime = $this->server->validateConnection();
|
||||
if ($uptime) {
|
||||
$this->emit('success', 'Server is reachable.');
|
||||
@ -80,7 +82,7 @@ public function validateServer($install = true)
|
||||
if ($uptime) {
|
||||
$install && $this->emit('success', 'Server is reachable.');
|
||||
} else {
|
||||
$install &&$this->emit('error', 'Server is not reachable. Please check your connection and configuration.');
|
||||
$install && $this->emit('error', 'Server is not reachable. Please check your connection and configuration.');
|
||||
return;
|
||||
}
|
||||
$dockerInstalled = $this->server->validateDockerEngine();
|
||||
@ -120,7 +122,14 @@ public function delete()
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->validate();
|
||||
if(isCloud() && !isDev()) {
|
||||
$this->validate();
|
||||
$this->validate([
|
||||
'server.ip' => 'required|ip',
|
||||
]);
|
||||
} else {
|
||||
$this->validate();
|
||||
}
|
||||
$uniqueIPs = Server::all()->reject(function (Server $server) {
|
||||
return $server->id === $this->server->id;
|
||||
})->pluck('ip')->toArray();
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Livewire\Server\Proxy;
|
||||
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
@ -11,18 +12,40 @@ class Deploy extends Component
|
||||
public Server $server;
|
||||
public bool $traefikDashboardAvailable = false;
|
||||
public ?string $currentRoute = null;
|
||||
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable', 'serverRefresh' => 'proxyStatusUpdated'];
|
||||
public ?string $serverIp = null;
|
||||
|
||||
public function mount() {
|
||||
protected $listeners = ['proxyStatusUpdated', 'traefikDashboardAvailable', 'serverRefresh' => 'proxyStatusUpdated', "checkProxy", "startProxy"];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
if ($this->server->id === 0) {
|
||||
$this->serverIp = base_ip();
|
||||
} else {
|
||||
$this->serverIp = $this->server->ip;
|
||||
}
|
||||
$this->currentRoute = request()->route()->getName();
|
||||
}
|
||||
public function traefikDashboardAvailable(bool $data) {
|
||||
public function traefikDashboardAvailable(bool $data)
|
||||
{
|
||||
$this->traefikDashboardAvailable = $data;
|
||||
}
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
$this->server->refresh();
|
||||
}
|
||||
public function ip()
|
||||
{
|
||||
}
|
||||
public function checkProxy()
|
||||
{
|
||||
try {
|
||||
CheckProxy::run($this->server);
|
||||
$this->emit('startProxyPolling');
|
||||
$this->emit('proxyChecked');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function startProxy()
|
||||
{
|
||||
try {
|
||||
|
@ -11,8 +11,6 @@ class Modal extends Component
|
||||
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
$this->server->proxy->set('status', 'running');
|
||||
$this->server->save();
|
||||
$this->emit('proxyStatusUpdated');
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Http\Livewire\Server\Proxy;
|
||||
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Jobs\ContainerStatusJob;
|
||||
use App\Models\Server;
|
||||
use Livewire\Component;
|
||||
@ -9,12 +10,42 @@
|
||||
class Status extends Component
|
||||
{
|
||||
public Server $server;
|
||||
public bool $polling = false;
|
||||
public int $numberOfPolls = 0;
|
||||
|
||||
protected $listeners = ['proxyStatusUpdated'];
|
||||
protected $listeners = ['proxyStatusUpdated', 'startProxyPolling'];
|
||||
public function startProxyPolling()
|
||||
{
|
||||
$this->polling = true;
|
||||
}
|
||||
public function proxyStatusUpdated()
|
||||
{
|
||||
$this->server->refresh();
|
||||
}
|
||||
public function checkProxy(bool $notification = false)
|
||||
{
|
||||
try {
|
||||
if ($this->polling) {
|
||||
if ($this->numberOfPolls >= 10) {
|
||||
$this->polling = false;
|
||||
$this->numberOfPolls = 0;
|
||||
$notification && $this->emit('error', 'Proxy is not running.');
|
||||
return;
|
||||
}
|
||||
$this->numberOfPolls++;
|
||||
}
|
||||
CheckProxy::run($this->server);
|
||||
$this->emit('proxyStatusUpdated');
|
||||
if ($this->server->proxy->status === 'running') {
|
||||
$this->polling = false;
|
||||
$notification && $this->emit('success', 'Proxy is running.');
|
||||
} else {
|
||||
$notification && $this->emit('error', 'Proxy is not running.');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function getProxyStatus()
|
||||
{
|
||||
try {
|
||||
@ -24,11 +55,4 @@ public function getProxyStatus()
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
public function getProxyStatusWithNoti()
|
||||
{
|
||||
if ($this->server->isFunctional()) {
|
||||
$this->emit('success', 'Refreshed proxy status.');
|
||||
$this->getProxyStatus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -78,10 +78,10 @@ public function backup_now()
|
||||
dispatch(new DatabaseBackupJob(
|
||||
backup: $this->backup
|
||||
));
|
||||
$this->emit('success', 'Backup queued. It will be available in a few minutes');
|
||||
$this->emit('success', 'Backup queued. It will be available in a few minutes.');
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
$this->emit('success', 'Backup updated successfully');
|
||||
$this->emit('success', 'Backup updated successfully.');
|
||||
}
|
||||
}
|
||||
|
@ -27,11 +27,6 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
public $tries = 1;
|
||||
public $timeout = 120;
|
||||
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public function middleware(): array
|
||||
{
|
||||
return [(new WithoutOverlapping($this->server->uuid))->dontRelease()];
|
||||
@ -41,6 +36,14 @@ public function uniqueId(): string
|
||||
{
|
||||
return $this->server->uuid;
|
||||
}
|
||||
|
||||
public function __construct(public Server $server)
|
||||
{
|
||||
if (isDev()) {
|
||||
$this->handle();
|
||||
}
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
try {
|
||||
|
@ -66,50 +66,77 @@ public function handle(): void
|
||||
ray('database not running');
|
||||
return;
|
||||
}
|
||||
$databaseType = $this->database->type();
|
||||
$databasesToBackup = data_get($this->backup, 'databases_to_backup');
|
||||
|
||||
if (is_null($databasesToBackup)) {
|
||||
if ($databaseType === 'standalone-postgresql') {
|
||||
$databasesToBackup = [$this->database->postgres_db];
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$databasesToBackup = explode(',', $databasesToBackup);
|
||||
$databasesToBackup = array_map('trim', $databasesToBackup);
|
||||
}
|
||||
$this->container_name = $this->database->uuid;
|
||||
$this->backup_dir = backup_dir() . "/databases/" . Str::of($this->team->name)->slug() . '-' . $this->team->id . '/' . $this->container_name;
|
||||
|
||||
if ($this->database->name === 'coolify-db') {
|
||||
$databasesToBackup = ['coolify'];
|
||||
$this->container_name = "coolify-db";
|
||||
$ip = Str::slug($this->server->ip);
|
||||
$this->backup_dir = backup_dir() . "/coolify" . "/coolify-db-$ip";
|
||||
}
|
||||
$this->backup_file = "/pg-backup-customformat-" . Carbon::now()->timestamp . ".backup";
|
||||
$this->backup_location = $this->backup_dir . $this->backup_file;
|
||||
|
||||
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||
'filename' => $this->backup_location,
|
||||
'scheduled_database_backup_id' => $this->backup->id,
|
||||
]);
|
||||
if ($this->database->type() === 'standalone-postgresql') {
|
||||
$this->backup_standalone_postgresql();
|
||||
foreach ($databasesToBackup as $database) {
|
||||
$size = 0;
|
||||
ray('Backing up ' . $database);
|
||||
try {
|
||||
$this->backup_file = "/pg-dump-$database-" . Carbon::now()->timestamp . ".dmp";
|
||||
$this->backup_location = $this->backup_dir . $this->backup_file;
|
||||
$this->backup_log = ScheduledDatabaseBackupExecution::create([
|
||||
'database_name' => $database,
|
||||
'filename' => $this->backup_location,
|
||||
'scheduled_database_backup_id' => $this->backup->id,
|
||||
]);
|
||||
if ($databaseType === 'standalone-postgresql') {
|
||||
$this->backup_standalone_postgresql($database);
|
||||
}
|
||||
$size = $this->calculate_size();
|
||||
$this->remove_old_backups();
|
||||
if ($this->backup->save_s3) {
|
||||
$this->upload_to_s3();
|
||||
}
|
||||
$this->team->notify(new BackupSuccess($this->backup, $this->database));
|
||||
$this->backup_log->update([
|
||||
'status' => 'success',
|
||||
'message' => $this->backup_output,
|
||||
'size' => $size,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->backup_log->update([
|
||||
'status' => 'failed',
|
||||
'message' => $this->backup_output,
|
||||
'size' => $size,
|
||||
'filename' => null
|
||||
]);
|
||||
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
||||
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
$this->calculate_size();
|
||||
$this->remove_old_backups();
|
||||
if ($this->backup->save_s3) {
|
||||
$this->upload_to_s3();
|
||||
}
|
||||
$this->save_backup_logs();
|
||||
$this->team->notify(new BackupSuccess($this->backup, $this->database));
|
||||
$this->backup_status = 'success';
|
||||
} catch (\Throwable $e) {
|
||||
$this->backup_status = 'failed';
|
||||
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
||||
$this->team->notify(new BackupFailed($this->backup, $this->database, $this->backup_output));
|
||||
throw $e;
|
||||
} finally {
|
||||
$this->backup_log->update([
|
||||
'status' => $this->backup_status,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function backup_standalone_postgresql(): void
|
||||
private function backup_standalone_postgresql(string $database): void
|
||||
{
|
||||
try {
|
||||
ray($this->backup_dir);
|
||||
$commands[] = "mkdir -p " . $this->backup_dir;
|
||||
$commands[] = "docker exec $this->container_name pg_dump -Fc -U {$this->database->postgres_user} > $this->backup_location";
|
||||
$commands[] = "docker exec $this->container_name pg_dump --format=custom --no-acl --no-owner --username {$this->database->postgres_user} $database > $this->backup_location";
|
||||
$this->backup_output = instant_remote_process($commands, $this->server);
|
||||
$this->backup_output = trim($this->backup_output);
|
||||
if ($this->backup_output === '') {
|
||||
@ -119,6 +146,7 @@ private function backup_standalone_postgresql(): void
|
||||
} catch (\Throwable $e) {
|
||||
$this->add_to_backup_output($e->getMessage());
|
||||
ray('Backup failed for ' . $this->container_name . ' at ' . $this->server->name . ':' . $this->backup_location . '\n\nError:' . $e->getMessage());
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
@ -131,9 +159,9 @@ private function add_to_backup_output($output): void
|
||||
}
|
||||
}
|
||||
|
||||
private function calculate_size(): void
|
||||
private function calculate_size()
|
||||
{
|
||||
$this->size = instant_remote_process(["du -b $this->backup_location | cut -f1"], $this->server);
|
||||
return instant_remote_process(["du -b $this->backup_location | cut -f1"], $this->server, false);
|
||||
}
|
||||
|
||||
private function remove_old_backups(): void
|
||||
@ -180,13 +208,4 @@ private function upload_to_s3(): void
|
||||
instant_remote_process([$command], $this->server);
|
||||
}
|
||||
}
|
||||
|
||||
private function save_backup_logs(): void
|
||||
{
|
||||
$this->backup_log->update([
|
||||
'status' => $this->backup_status,
|
||||
'message' => $this->backup_output,
|
||||
'size' => $this->size,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -47,20 +47,7 @@ public function handle(): void
|
||||
if (!$this->server->isFunctional()) {
|
||||
return;
|
||||
}
|
||||
if (isDev()) {
|
||||
$this->dockerRootFilesystem = "/";
|
||||
} else {
|
||||
$this->dockerRootFilesystem = instant_remote_process(
|
||||
[
|
||||
"stat --printf=%m $(docker info --format '{{json .DockerRootDir}}'' |sed 's/\"//g')"
|
||||
],
|
||||
$this->server,
|
||||
false
|
||||
);
|
||||
}
|
||||
if (!$this->dockerRootFilesystem) {
|
||||
return;
|
||||
}
|
||||
$this->dockerRootFilesystem = "/";
|
||||
$this->usageBefore = $this->getFilesystemUsage();
|
||||
if ($this->usageBefore >= $this->server->settings->cleanup_after_percentage) {
|
||||
ray('Cleaning up ' . $this->server->name)->color('orange');
|
||||
|
@ -14,7 +14,7 @@ class Environment extends Model
|
||||
|
||||
public function can_delete_environment()
|
||||
{
|
||||
return $this->applications()->count() == 0 && $this->postgresqls()->count() == 0 && $this->services()->count() == 0;
|
||||
return $this->applications()->count() == 0 && $this->redis()->count() == 0 && $this->postgresqls()->count() == 0 && $this->services()->count() == 0;
|
||||
}
|
||||
|
||||
public function applications()
|
||||
@ -26,10 +26,16 @@ public function postgresqls()
|
||||
{
|
||||
return $this->hasMany(StandalonePostgresql::class);
|
||||
}
|
||||
public function redis()
|
||||
{
|
||||
return $this->hasMany(StandaloneRedis::class);
|
||||
}
|
||||
|
||||
public function databases()
|
||||
{
|
||||
return $this->postgresqls();
|
||||
$postgresqls = $this->postgresqls;
|
||||
$redis = $this->redis;
|
||||
return $postgresqls->concat($redis);
|
||||
}
|
||||
|
||||
public function project()
|
||||
|
@ -52,4 +52,8 @@ public function postgresqls()
|
||||
{
|
||||
return $this->hasManyThrough(StandalonePostgresql::class, Environment::class);
|
||||
}
|
||||
public function redis()
|
||||
{
|
||||
return $this->hasManyThrough(StandaloneRedis::class, Environment::class);
|
||||
}
|
||||
}
|
||||
|
@ -123,7 +123,8 @@ public function databases()
|
||||
{
|
||||
return $this->destinations()->map(function ($standaloneDocker) {
|
||||
$postgresqls = $standaloneDocker->postgresqls;
|
||||
return $postgresqls?->concat([]) ?? collect([]);
|
||||
$redis = $standaloneDocker->redis;
|
||||
return $postgresqls->concat($redis);
|
||||
})->flatten();
|
||||
}
|
||||
public function applications()
|
||||
|
@ -15,6 +15,10 @@ public function postgresqls()
|
||||
{
|
||||
return $this->morphMany(StandalonePostgresql::class, 'destination');
|
||||
}
|
||||
public function redis()
|
||||
{
|
||||
return $this->morphMany(StandaloneRedis::class, 'destination');
|
||||
}
|
||||
|
||||
public function server()
|
||||
{
|
||||
|
103
app/Models/StandaloneRedis.php
Normal file
103
app/Models/StandaloneRedis.php
Normal file
@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class StandaloneRedis extends BaseModel
|
||||
{
|
||||
use HasFactory;
|
||||
protected $guarded = [];
|
||||
|
||||
protected static function booted()
|
||||
{
|
||||
static::created(function ($database) {
|
||||
LocalPersistentVolume::create([
|
||||
'name' => 'redis-data-' . $database->uuid,
|
||||
'mount_path' => '/data',
|
||||
'host_path' => null,
|
||||
'resource_id' => $database->id,
|
||||
'resource_type' => $database->getMorphClass(),
|
||||
'is_readonly' => true
|
||||
]);
|
||||
});
|
||||
static::deleting(function ($database) {
|
||||
// Stop Container
|
||||
instant_remote_process(
|
||||
["docker rm -f {$database->uuid}"],
|
||||
$database->destination->server,
|
||||
false
|
||||
);
|
||||
// Stop TCP Proxy
|
||||
if ($database->is_public) {
|
||||
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server, false);
|
||||
}
|
||||
$database->scheduledBackups()->delete();
|
||||
$database->persistentStorages()->delete();
|
||||
$database->environment_variables()->delete();
|
||||
// Remove Volume
|
||||
instant_remote_process(['docker volume rm postgres-data-' . $database->uuid], $database->destination->server, false);
|
||||
});
|
||||
}
|
||||
|
||||
public function portsMappings(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
set: fn ($value) => $value === "" ? null : $value,
|
||||
);
|
||||
}
|
||||
|
||||
// Normal Deployments
|
||||
|
||||
public function portsMappingsArray(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => is_null($this->ports_mappings)
|
||||
? []
|
||||
: explode(',', $this->ports_mappings),
|
||||
|
||||
);
|
||||
}
|
||||
|
||||
public function type(): string
|
||||
{
|
||||
return 'standalone-redis';
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
<?php
|
||||
|
||||
const DATABASE_TYPES = ['postgresql'];
|
||||
const DATABASE_TYPES = ['postgresql','redis'];
|
||||
const VALID_CRON_STRINGS = [
|
||||
'every_minute' => '* * * * *',
|
||||
'hourly' => '0 * * * *',
|
||||
|
@ -3,6 +3,7 @@
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
function generate_database_name(string $type): string
|
||||
@ -27,6 +28,21 @@ function create_standalone_postgresql($environment_id, $destination_uuid): Stand
|
||||
]);
|
||||
}
|
||||
|
||||
function create_standalone_redis($environment_id, $destination_uuid): StandaloneRedis
|
||||
{
|
||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||
if (!$destination) {
|
||||
throw new Exception('Destination not found');
|
||||
}
|
||||
return StandaloneRedis::create([
|
||||
'name' => generate_database_name('redis'),
|
||||
'redis_password' => \Illuminate\Support\Str::password(symbols: false),
|
||||
'environment_id' => $environment_id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete file locally on the filesystem.
|
||||
* @param string $filename
|
||||
|
@ -3,6 +3,7 @@
|
||||
use App\Actions\Proxy\SaveConfiguration;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
function get_proxy_path()
|
||||
@ -21,7 +22,7 @@ function connectProxyToNetworks(Server $server)
|
||||
}
|
||||
$commands = $networks->map(function ($network) {
|
||||
return [
|
||||
"echo '####### Connecting coolify-proxy to $network network...'",
|
||||
"echo 'Connecting coolify-proxy to $network network...'",
|
||||
"docker network ls --format '{{.Name}}' | grep '^$network$' >/dev/null || docker network create --attachable $network >/dev/null",
|
||||
"docker network connect $network coolify-proxy >/dev/null 2>&1 || true",
|
||||
];
|
||||
@ -187,8 +188,14 @@ function setup_default_redirect_404(string|null $redirect_url, Server $server)
|
||||
}
|
||||
}
|
||||
|
||||
function startPostgresProxy(StandalonePostgresql $database)
|
||||
function startDatabaseProxy(StandalonePostgresql|StandaloneRedis $database)
|
||||
{
|
||||
$internalPort = null;
|
||||
if ($database->getMorphClass()=== 'App\Models\StandaloneRedis') {
|
||||
$internalPort = 6379;
|
||||
} else if ($database->getMorphClass()=== 'App\Models\StandalonePostgresql') {
|
||||
$internalPort = 5432;
|
||||
}
|
||||
$containerName = "{$database->uuid}-proxy";
|
||||
$configuration_dir = database_proxy_dir($database->uuid);
|
||||
$nginxconf = <<<EOF
|
||||
@ -203,7 +210,7 @@ function startPostgresProxy(StandalonePostgresql $database)
|
||||
stream {
|
||||
server {
|
||||
listen $database->public_port;
|
||||
proxy_pass $database->uuid:5432;
|
||||
proxy_pass $database->uuid:$internalPort;
|
||||
}
|
||||
}
|
||||
EOF;
|
||||
@ -260,7 +267,7 @@ function startPostgresProxy(StandalonePostgresql $database)
|
||||
"docker compose --project-directory {$configuration_dir} up --build -d >/dev/null",
|
||||
], $database->destination->server);
|
||||
}
|
||||
function stopPostgresProxy(StandalonePostgresql $database)
|
||||
function stopDatabaseProxy(StandalonePostgresql|StandaloneRedis $database)
|
||||
{
|
||||
instant_remote_process(["docker rm -f {$database->uuid}-proxy"], $database->destination->server);
|
||||
}
|
||||
|
@ -108,12 +108,13 @@ function generateSshCommand(Server $server, string $command, bool $isMux = true)
|
||||
}
|
||||
function instant_remote_process(Collection|array $command, Server $server, $throwError = true)
|
||||
{
|
||||
$timeout = config('constants.ssh.command_timeout');
|
||||
if ($command instanceof Collection) {
|
||||
$command = $command->toArray();
|
||||
}
|
||||
$command_string = implode("\n", $command);
|
||||
$ssh_command = generateSshCommand($server, $command_string);
|
||||
$process = Process::run($ssh_command);
|
||||
$process = Process::timeout($timeout)->run($ssh_command);
|
||||
$output = trim($process->output());
|
||||
$exitCode = $process->exitCode();
|
||||
if ($exitCode !== 0) {
|
||||
|
@ -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.81',
|
||||
'release' => '4.0.0-beta.82',
|
||||
// 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.81';
|
||||
return '4.0.0-beta.82';
|
||||
|
@ -0,0 +1,54 @@
|
||||
<?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_redis', function (Blueprint $table) {
|
||||
$table->id();
|
||||
$table->string('uuid')->unique();
|
||||
$table->string('name');
|
||||
$table->string('description')->nullable();
|
||||
|
||||
$table->text('redis_password');
|
||||
$table->longText('redis_conf')->nullable();
|
||||
|
||||
$table->string('status')->default('exited');
|
||||
|
||||
$table->string('image')->default('redis:7.2');
|
||||
|
||||
$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("0");
|
||||
$table->integer('limits_cpu_shares')->default(1024);
|
||||
|
||||
$table->timestamp('started_at')->nullable();
|
||||
$table->morphs('destination');
|
||||
$table->foreignId('environment_id')->nullable();
|
||||
$table->timestamps();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::dropIfExists('standalone_redis');
|
||||
}
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('environment_variables', function (Blueprint $table) {
|
||||
$table->foreignId('standalone_redis_id')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('environment_variables', function (Blueprint $table) {
|
||||
$table->dropColumn('standalone_redis_id');
|
||||
});
|
||||
}
|
||||
};
|
@ -0,0 +1,34 @@
|
||||
<?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('scheduled_database_backups', function (Blueprint $table) {
|
||||
$table->text('databases_to_backup')->nullable();
|
||||
});
|
||||
Schema::table('scheduled_database_backup_executions', function (Blueprint $table) {
|
||||
$table->string('database_name')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('scheduled_database_backups', function (Blueprint $table) {
|
||||
$table->dropColumn('databases_to_backup');
|
||||
});
|
||||
Schema::table('scheduled_database_backup_executions', function (Blueprint $table) {
|
||||
$table->dropColumn('database_name');
|
||||
});
|
||||
}
|
||||
};
|
23
database/seeders/StandaloneRedisSeeder.php
Normal file
23
database/seeders/StandaloneRedisSeeder.php
Normal file
@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Database\Seeders;
|
||||
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Illuminate\Database\Seeder;
|
||||
|
||||
class StandaloneRedisSeeder extends Seeder
|
||||
{
|
||||
public function run(): void
|
||||
{
|
||||
StandaloneRedis::create([
|
||||
'name' => 'Local PostgreSQL',
|
||||
'description' => 'Local PostgreSQL for testing',
|
||||
'redis_password' => 'redis',
|
||||
'environment_id' => 1,
|
||||
'destination_id' => 0,
|
||||
'destination_type' => StandaloneDocker::class,
|
||||
]);
|
||||
}
|
||||
}
|
@ -7,14 +7,13 @@
|
||||
href="{{ route('project.database.logs', $parameters) }}">
|
||||
<button>Logs</button>
|
||||
</a>
|
||||
<a class="{{ request()->routeIs('project.database.backups.all') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.database.backups.all', $parameters) }}">
|
||||
<button>Backups</button>
|
||||
</a>
|
||||
{{-- <x-applications.links :application="$application" /> --}}
|
||||
@if ($database->getMorphClass() === 'App\Models\StandalonePostgresql')
|
||||
<a class="{{ request()->routeIs('project.database.backups.all') ? 'text-white' : '' }}"
|
||||
href="{{ route('project.database.backups.all', $parameters) }}">
|
||||
<button>Backups</button>
|
||||
</a>
|
||||
@endif
|
||||
<div class="flex-1"></div>
|
||||
{{-- <x-applications.advanced :application="$application" /> --}}
|
||||
|
||||
@if ($database->status !== 'exited')
|
||||
<button wire:click='stop' class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2"
|
||||
|
@ -26,6 +26,7 @@
|
||||
</div>
|
||||
@endif
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Databases To Backup" helper="Comma separated list of databases to backup. Empty will include the default one." id="backup.databases_to_backup" />
|
||||
<x-forms.input label="Frequency" id="backup.frequency" />
|
||||
<x-forms.input label="Number of backups to keep (locally)" id="backup.number_of_backups_locally" />
|
||||
</div>
|
||||
|
@ -1,18 +1,19 @@
|
||||
<div class="flex flex-col flex-col-reverse gap-2">
|
||||
<div class="flex flex-col-reverse gap-2">
|
||||
@forelse($executions as $execution)
|
||||
<form class="flex flex-col p-2 border-dotted border-1 bg-coolgray-300" @class([
|
||||
'border-green-500' => data_get($execution, 'status') === 'success',
|
||||
'border-red-500' => data_get($execution, 'status') === 'failed',
|
||||
])>
|
||||
<div>Started At: {{ data_get($execution, 'created_at') }}</div>
|
||||
<div>Database: {{ data_get($execution, 'database_name', 'N/A') }}</div>
|
||||
<div>Status: {{ data_get($execution, 'status') }}</div>
|
||||
<div>Started At: {{ data_get($execution, 'created_at') }}</div>
|
||||
@if (data_get($execution, 'message'))
|
||||
<div>Message: {{ data_get($execution, 'message') }}</div>
|
||||
@endif
|
||||
<div>Size: {{ data_get($execution, 'size') }} B / {{ round((int) data_get($execution, 'size') / 1024, 2) }}
|
||||
kB / {{ round((int) data_get($execution, 'size') / 1024 / 1024, 2) }} MB
|
||||
kB / {{ round((int) data_get($execution, 'size') / 1024 / 1024, 3) }} MB
|
||||
</div>
|
||||
<div>Location: {{ data_get($execution, 'filename') }}</div>
|
||||
<div>Location: {{ data_get($execution, 'filename', 'N/A') }}</div>
|
||||
<livewire:project.database.backup-execution :execution="$execution" :wire:key="$execution->id" />
|
||||
</form>
|
||||
@empty
|
||||
|
@ -62,7 +62,7 @@
|
||||
label="Public Port" />
|
||||
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
||||
</div>
|
||||
<x-forms.input label="Postgres URL" readonly wire:model="db_url" />
|
||||
<x-forms.input label="Postgres URL" type="password" readonly wire:model="db_url" />
|
||||
</div>
|
||||
</form>
|
||||
<div class="pb-16">
|
||||
|
@ -0,0 +1,28 @@
|
||||
<div>
|
||||
<form wire:submit.prevent="submit" class="flex flex-col gap-2">
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>General</h2>
|
||||
<x-forms.button type="submit">
|
||||
Save
|
||||
</x-forms.button>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input label="Name" id="database.name" />
|
||||
<x-forms.input label="Description" id="database.description" />
|
||||
<x-forms.input label="Image" id="database.image" required
|
||||
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/postgres'>https://hub.docker.com/_/postgres</a>" />
|
||||
</div>
|
||||
<div class="flex flex-col gap-2">
|
||||
<h3 class="py-2">Network</h3>
|
||||
<div class="flex items-end gap-2">
|
||||
<x-forms.input placeholder="3000:5432" id="database.ports_mappings" label="Ports Mappings"
|
||||
helper="A comma separated list of ports you would like to map to the host system.<br><span class='inline-block font-bold text-warning'>Example</span>3000:5432,3002:5433" />
|
||||
<x-forms.input placeholder="5432" disabled="{{ $database->is_public }}" id="database.public_port"
|
||||
label="Public Port" />
|
||||
<x-forms.checkbox instantSave id="database.is_public" label="Accessible over the internet" />
|
||||
</div>
|
||||
<x-forms.input label="Redis URL" type="password" readonly wire:model="db_url" />
|
||||
</div>
|
||||
<x-forms.textarea helper="<a target='_blank' class='text-white underline' href='https://raw.githubusercontent.com/redis/redis/7.2/redis.conf'>Redis Default Configuration</a>" label="Custom Redis Configuration" rows="10" id="database.redis_conf" />
|
||||
</form>
|
||||
</div>
|
@ -94,6 +94,16 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="box group" wire:click="setType('redis')">
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="font-bold text-white group-hover:text-white">
|
||||
New Redis
|
||||
</div>
|
||||
<div class="text-xs group-hover:text-white">
|
||||
The open source, in-memory data store for cache, streaming engine, and message broker.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{-- <div class="box group" wire:click="setType('existing-postgresql')">
|
||||
<div class="flex flex-col mx-6">
|
||||
<div class="group-hover:text-white">
|
||||
|
@ -1,7 +1,8 @@
|
||||
<div>
|
||||
@if (
|
||||
$resource->getMorphClass() == 'App\Models\Application' ||
|
||||
$resource->getMorphClass() == 'App\Models\StandalonePostgresql')
|
||||
$resource->getMorphClass() == 'App\Models\StandalonePostgresql' ||
|
||||
$resource->getMorphClass() == 'App\Models\StandaloneRedis')
|
||||
<div class="flex items-center gap-2">
|
||||
<h2>Storages</h2>
|
||||
<x-helper
|
||||
|
@ -9,11 +9,7 @@
|
||||
<h1>Logs</h1>
|
||||
<livewire:project.database.heading :database="$resource" />
|
||||
<div class="pt-4">
|
||||
@if (Str::of($status)->startsWith('running'))
|
||||
<livewire:project.shared.get-logs :server="$server" :container="$container" />
|
||||
@else
|
||||
Database is not running.
|
||||
@endif
|
||||
<livewire:project.shared.get-logs :server="$server" :container="$container" />
|
||||
</div>
|
||||
@elseif ($type === 'service')
|
||||
<livewire:project.service.navbar :service="$resource" :parameters="$parameters" :query="$query" />
|
||||
|
@ -12,7 +12,7 @@
|
||||
<div class="flex gap-4">
|
||||
@if ($currentRoute === 'server.proxy' && $traefikDashboardAvailable)
|
||||
<button>
|
||||
<a target="_blank" href="http://{{ $server->ip }}:8080">
|
||||
<a target="_blank" href="http://{{ $serverIp }}:8080">
|
||||
Traefik Dashboard
|
||||
<x-external-link />
|
||||
</a>
|
||||
@ -20,8 +20,9 @@
|
||||
@endif
|
||||
<x-forms.button isModal noStyle modalId="stopProxy"
|
||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24" stroke-width="2"
|
||||
stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-error" viewBox="0 0 24 24"
|
||||
stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
stroke-linejoin="round">
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M6 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path>
|
||||
<path d="M14 5m0 1a1 1 0 0 1 1 -1h2a1 1 0 0 1 1 1v12a1 1 0 0 1 -1 1h-2a1 1 0 0 1 -1 -1z"></path>
|
||||
@ -30,7 +31,7 @@ class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"
|
||||
</x-forms.button>
|
||||
</div>
|
||||
@else
|
||||
<button wire:click='startProxy' onclick="startProxy.showModal()"
|
||||
<button onclick="checkProxy()"
|
||||
class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" class="w-5 h-5 text-warning" viewBox="0 0 24 24"
|
||||
stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round"
|
||||
@ -42,4 +43,13 @@ class="flex items-center gap-2 cursor-pointer hover:text-white text-neutral-400"
|
||||
</button>
|
||||
@endif
|
||||
@endif
|
||||
<script>
|
||||
function checkProxy() {
|
||||
Livewire.emit('checkProxy')
|
||||
}
|
||||
Livewire.on('proxyChecked', () => {
|
||||
startProxy.showModal();
|
||||
Livewire.emit('startProxy');
|
||||
})
|
||||
</script>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div>
|
||||
@if ($server->isFunctional())
|
||||
<div class="flex gap-2" x-init="$wire.getProxyStatus">
|
||||
<div class="flex gap-2" @if ($polling) wire:poll.2000ms='checkProxy' @endif>
|
||||
@if (data_get($server, 'proxy.status') === 'running')
|
||||
<x-status.running status="Proxy Running" />
|
||||
@elseif (data_get($server, 'proxy.status') === 'restarting')
|
||||
@ -8,7 +8,9 @@
|
||||
@else
|
||||
<x-status.stopped status="Proxy Stopped" />
|
||||
@endif
|
||||
<x-forms.button wire:click='getProxyStatusWithNoti'>Refresh </x-forms.button>
|
||||
@if (data_get($server, 'proxy.status') === 'running')
|
||||
<x-forms.button wire:click='checkProxy(true)'>Refresh</x-forms.button>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
@ -13,25 +13,23 @@
|
||||
</x-modal>
|
||||
<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-4 min-w-fit">
|
||||
<a :class="activeTab === 'general' && 'text-white'"
|
||||
@click.prevent="activeTab = 'general'; window.location.hash = 'general'" href="#">General</a>
|
||||
<a :class="activeTab === 'general' && 'text-white'" @click.prevent="activeTab = 'general';
|
||||
window.location.hash = 'general'" href="#">General</a>
|
||||
<a :class="activeTab === 'environment-variables' && 'text-white'"
|
||||
@click.prevent="activeTab = 'environment-variables'; window.location.hash = 'environment-variables'"
|
||||
href="#">Environment
|
||||
Variables</a>
|
||||
<a :class="activeTab === 'server' && 'text-white'"
|
||||
@click.prevent="activeTab = 'server'; window.location.hash = 'server'"
|
||||
href="#">Server
|
||||
<a :class="activeTab === 'server' && 'text-white'" @click.prevent="activeTab = 'server';
|
||||
window.location.hash = 'server'" href="#">Server
|
||||
</a>
|
||||
<a :class="activeTab === 'storages' && 'text-white'"
|
||||
@click.prevent="activeTab = 'storages'; window.location.hash = 'storages'" href="#">Storages
|
||||
<a :class="activeTab === 'storages' && 'text-white'" @click.prevent="activeTab = 'storages';
|
||||
window.location.hash = 'storages'" href="#">Storages
|
||||
</a>
|
||||
<a :class="activeTab === 'resource-limits' && 'text-white'"
|
||||
@click.prevent="activeTab = 'resource-limits'; window.location.hash = 'resource-limits'"
|
||||
href="#">Resource Limits
|
||||
<a :class="activeTab === 'resource-limits' && 'text-white'" @click.prevent="activeTab = 'resource-limits';
|
||||
window.location.hash = 'resource-limits'" href="#">Resource Limits
|
||||
</a>
|
||||
<a :class="activeTab === 'danger' && 'text-white'"
|
||||
@click.prevent="activeTab = 'danger'; window.location.hash = 'danger'" href="#">Danger Zone
|
||||
<a :class="activeTab === 'danger' && 'text-white'" @click.prevent="activeTab = 'danger';
|
||||
window.location.hash = 'danger'" href="#">Danger Zone
|
||||
</a>
|
||||
</div>
|
||||
<div class="w-full pl-8">
|
||||
@ -39,6 +37,9 @@
|
||||
@if ($database->type() === 'standalone-postgresql')
|
||||
<livewire:project.database.postgresql.general :database="$database" />
|
||||
@endif
|
||||
@if ($database->type() === 'standalone-redis')
|
||||
<livewire:project.database.redis.general :database="$database" />
|
||||
@endif
|
||||
</div>
|
||||
<div x-cloak x-show="activeTab === 'environment-variables'">
|
||||
<livewire:project.shared.environment-variable.all :resource="$database" />
|
||||
|
@ -46,7 +46,7 @@ class="items-center justify-center box">+ Add New Resource</a>
|
||||
</div>
|
||||
</a>
|
||||
@endforeach
|
||||
@foreach ($environment->databases->sortBy('name') as $databases)
|
||||
@foreach ($environment->databases()->sortBy('name') as $databases)
|
||||
<a class="box group"
|
||||
href="{{ route('project.database.configuration', [$project->uuid, $environment->name, $databases->uuid]) }}">
|
||||
<div class="flex flex-col mx-6">
|
||||
|
@ -4,7 +4,7 @@
|
||||
"version": "3.12.36"
|
||||
},
|
||||
"v4": {
|
||||
"version": "4.0.0-beta.81"
|
||||
"version": "4.0.0-beta.82"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user