Merge branch 'next' into docker-network-aliases
This commit is contained in:
@@ -7,6 +7,7 @@ use App\Models\PrivateKey;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\Team;
|
||||
use App\Services\ConfigurationRepository;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Component;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
@@ -266,7 +267,7 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
public function validateServer()
|
||||
{
|
||||
try {
|
||||
config()->set('constants.ssh.mux_enabled', false);
|
||||
$this->disableSshMux();
|
||||
|
||||
// EC2 does not have `uptime` command, lol
|
||||
instant_remote_process(['ls /'], $this->createdServer, true);
|
||||
@@ -376,6 +377,12 @@ uZx9iFkCELtxrh31QJ68AAAAEXNhaWxANzZmZjY2ZDJlMmRkAQIDBA==
|
||||
['private' => $this->privateKey, 'public' => $this->publicKey] = generateSSHKey();
|
||||
}
|
||||
|
||||
private function disableSshMux(): void
|
||||
{
|
||||
$configRepository = app(ConfigurationRepository::class);
|
||||
$configRepository->disableSshMux();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.boarding.index')->layout('layouts.boarding');
|
||||
|
||||
@@ -56,6 +56,9 @@ class Discord extends Component
|
||||
#[Validate(['boolean'])]
|
||||
public bool $serverUnreachableDiscordNotifications = true;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $discordPingEnabled = true;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
try {
|
||||
@@ -87,6 +90,8 @@ class Discord extends Component
|
||||
$this->settings->server_reachable_discord_notifications = $this->serverReachableDiscordNotifications;
|
||||
$this->settings->server_unreachable_discord_notifications = $this->serverUnreachableDiscordNotifications;
|
||||
|
||||
$this->settings->discord_ping_enabled = $this->discordPingEnabled;
|
||||
|
||||
$this->settings->save();
|
||||
refreshSession();
|
||||
} else {
|
||||
@@ -105,12 +110,30 @@ class Discord extends Component
|
||||
$this->serverDiskUsageDiscordNotifications = $this->settings->server_disk_usage_discord_notifications;
|
||||
$this->serverReachableDiscordNotifications = $this->settings->server_reachable_discord_notifications;
|
||||
$this->serverUnreachableDiscordNotifications = $this->settings->server_unreachable_discord_notifications;
|
||||
|
||||
$this->discordPingEnabled = $this->settings->discord_ping_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveDiscordPingEnabled()
|
||||
{
|
||||
try {
|
||||
$original = $this->discordPingEnabled;
|
||||
$this->validate([
|
||||
'discordPingEnabled' => 'required',
|
||||
]);
|
||||
$this->saveModel();
|
||||
} catch (\Throwable $e) {
|
||||
$this->discordPingEnabled = $original;
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveDiscordEnabled()
|
||||
{
|
||||
try {
|
||||
$original = $this->discordEnabled;
|
||||
$this->validate([
|
||||
'discordWebhookUrl' => 'required',
|
||||
], [
|
||||
@@ -118,7 +141,7 @@ class Discord extends Component
|
||||
]);
|
||||
$this->saveModel();
|
||||
} catch (\Throwable $e) {
|
||||
$this->discordEnabled = false;
|
||||
$this->discordEnabled = $original;
|
||||
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ class Index extends Component
|
||||
$this->current_password = '';
|
||||
$this->new_password = '';
|
||||
$this->new_password_confirmation = '';
|
||||
$this->dispatch('reloadWindow');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ class Configuration extends Component
|
||||
public function mount()
|
||||
{
|
||||
$this->currentRoute = request()->route()->getName();
|
||||
|
||||
$project = currentTeam()
|
||||
->projects()
|
||||
->select('id', 'uuid', 'team_id')
|
||||
@@ -39,6 +40,9 @@ class Configuration extends Component
|
||||
$this->project = $project;
|
||||
$this->environment = $environment;
|
||||
$this->application = $application;
|
||||
if ($this->application->build_pack === 'dockercompose' && $this->currentRoute === 'project.application.healthcheck') {
|
||||
return redirect()->route('project.application.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]);
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
|
||||
@@ -87,6 +87,7 @@ class General extends Component
|
||||
'application.post_deployment_command_container' => 'nullable',
|
||||
'application.custom_nginx_configuration' => 'nullable',
|
||||
'application.settings.is_static' => 'boolean|required',
|
||||
'application.settings.is_spa' => 'boolean|required',
|
||||
'application.settings.is_build_server_enabled' => 'boolean|required',
|
||||
'application.settings.is_container_label_escape_enabled' => 'boolean|required',
|
||||
'application.settings.is_container_label_readonly_enabled' => 'boolean|required',
|
||||
@@ -126,6 +127,7 @@ class General extends Component
|
||||
'application.docker_compose_custom_build_command' => 'Docker compose custom build command',
|
||||
'application.custom_nginx_configuration' => 'Custom Nginx configuration',
|
||||
'application.settings.is_static' => 'Is static',
|
||||
'application.settings.is_spa' => 'Is SPA',
|
||||
'application.settings.is_build_server_enabled' => 'Is build server enabled',
|
||||
'application.settings.is_container_label_escape_enabled' => 'Is container label escape enabled',
|
||||
'application.settings.is_container_label_readonly_enabled' => 'Is container label readonly',
|
||||
@@ -173,6 +175,9 @@ class General extends Component
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
if ($this->application->settings->isDirty('is_spa')) {
|
||||
$this->generateNginxConfiguration($this->application->settings->is_spa ? 'spa' : 'static');
|
||||
}
|
||||
$this->application->settings->save();
|
||||
$this->dispatch('success', 'Settings saved.');
|
||||
$this->application->refresh();
|
||||
@@ -192,6 +197,7 @@ class General extends Component
|
||||
if ($this->application->settings->is_container_label_readonly_enabled) {
|
||||
$this->resetDefaultLabels(false);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function loadComposeFile($isInit = false)
|
||||
@@ -289,9 +295,9 @@ class General extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function generateNginxConfiguration()
|
||||
public function generateNginxConfiguration($type = 'static')
|
||||
{
|
||||
$this->application->custom_nginx_configuration = defaultNginxConfiguration();
|
||||
$this->application->custom_nginx_configuration = defaultNginxConfiguration($type);
|
||||
$this->application->save();
|
||||
$this->dispatch('success', 'Nginx configuration generated.');
|
||||
}
|
||||
@@ -371,6 +377,9 @@ class General extends Component
|
||||
if ($this->application->isDirty('redirect')) {
|
||||
$this->setRedirect();
|
||||
}
|
||||
if ($this->application->isDirty('dockerfile')) {
|
||||
$this->application->parseHealthcheckFromDockerfile($this->application->dockerfile);
|
||||
}
|
||||
|
||||
$this->checkFqdns();
|
||||
|
||||
@@ -448,7 +457,6 @@ class General extends Component
|
||||
{
|
||||
$config = GenerateConfig::run($this->application, true);
|
||||
$fileName = str($this->application->name)->slug()->append('_config.json');
|
||||
dd($config);
|
||||
|
||||
return response()->streamDownload(function () use ($config) {
|
||||
echo $config;
|
||||
|
||||
@@ -4,8 +4,11 @@ namespace App\Livewire\Project\Database\Dragonfly;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Helpers\SslHelper;
|
||||
use App\Models\Server;
|
||||
use App\Models\SslCertificate;
|
||||
use App\Models\StandaloneDragonfly;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Attributes\Validate;
|
||||
@@ -50,12 +53,19 @@ class General extends Component
|
||||
#[Validate(['nullable', 'boolean'])]
|
||||
public bool $isLogDrainEnabled = false;
|
||||
|
||||
public ?Carbon $certificateValidUntil = null;
|
||||
|
||||
#[Validate(['nullable', 'boolean'])]
|
||||
public bool $enable_ssl = false;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$userId = Auth::id();
|
||||
$teamId = Auth::user()->currentTeam()->id;
|
||||
|
||||
return [
|
||||
"echo-private:team.{$teamId},DatabaseProxyStopped" => 'databaseProxyStopped',
|
||||
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -64,6 +74,12 @@ class General extends Component
|
||||
try {
|
||||
$this->syncData();
|
||||
$this->server = data_get($this->database, 'destination.server');
|
||||
|
||||
$existingCert = $this->database->sslCertificates()->first();
|
||||
|
||||
if ($existingCert) {
|
||||
$this->certificateValidUntil = $existingCert->valid_until;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -82,6 +98,7 @@ class General extends Component
|
||||
$this->database->public_port = $this->publicPort;
|
||||
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
|
||||
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
|
||||
$this->database->enable_ssl = $this->enable_ssl;
|
||||
$this->database->save();
|
||||
|
||||
$this->dbUrl = $this->database->internal_db_url;
|
||||
@@ -96,6 +113,7 @@ class General extends Component
|
||||
$this->publicPort = $this->database->public_port;
|
||||
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
|
||||
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
|
||||
$this->enable_ssl = $this->database->enable_ssl;
|
||||
$this->dbUrl = $this->database->internal_db_url;
|
||||
$this->dbUrlPublic = $this->database->external_db_url;
|
||||
}
|
||||
@@ -174,4 +192,61 @@ class General extends Component
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveSSL()
|
||||
{
|
||||
try {
|
||||
$this->syncData(true);
|
||||
$this->dispatch('success', 'SSL configuration updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function regenerateSslCertificate()
|
||||
{
|
||||
try {
|
||||
$existingCert = $this->database->sslCertificates()->first();
|
||||
|
||||
if (! $existingCert) {
|
||||
$this->dispatch('error', 'No existing SSL certificate found for this database.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$server = $this->database->destination->server;
|
||||
|
||||
$caCert = SslCertificate::where('server_id', $server->id)
|
||||
->where('is_ca_certificate', true)
|
||||
->first();
|
||||
|
||||
if (! $caCert) {
|
||||
$server->generateCaCertificate();
|
||||
$caCert = SslCertificate::where('server_id', $server->id)->where('is_ca_certificate', true)->first();
|
||||
}
|
||||
|
||||
if (! $caCert) {
|
||||
$this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
SslHelper::generateSslCertificate(
|
||||
commonName: $existingCert->commonName,
|
||||
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
|
||||
resourceType: $existingCert->resource_type,
|
||||
resourceId: $existingCert->resource_id,
|
||||
serverId: $existingCert->server_id,
|
||||
caCert: $caCert->ssl_certificate,
|
||||
caKey: $caCert->ssl_private_key,
|
||||
configurationDir: $existingCert->configuration_dir,
|
||||
mountPath: $existingCert->mount_path,
|
||||
isPemKeyFileRequired: true,
|
||||
);
|
||||
|
||||
$this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.');
|
||||
} catch (Exception $e) {
|
||||
handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ class Heading extends Component
|
||||
$this->database->update([
|
||||
'started_at' => now(),
|
||||
]);
|
||||
$this->dispatch('refresh');
|
||||
$this->check_status();
|
||||
|
||||
if (is_null($this->database->config_hash) || $this->database->isConfigurationChanged()) {
|
||||
$this->database->isConfigurationChanged(true);
|
||||
$this->dispatch('configurationChanged');
|
||||
@@ -44,7 +44,7 @@ class Heading extends Component
|
||||
public function check_status($showNotification = false)
|
||||
{
|
||||
if ($this->database->destination->server->isFunctional()) {
|
||||
GetContainersStatus::dispatch($this->database->destination->server);
|
||||
GetContainersStatus::run($this->database->destination->server);
|
||||
}
|
||||
|
||||
if ($showNotification) {
|
||||
@@ -63,6 +63,7 @@ class Heading extends Component
|
||||
$this->database->status = 'exited';
|
||||
$this->database->save();
|
||||
$this->check_status();
|
||||
$this->dispatch('refresh');
|
||||
}
|
||||
|
||||
public function restart()
|
||||
|
||||
@@ -4,8 +4,11 @@ namespace App\Livewire\Project\Database\Keydb;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Helpers\SslHelper;
|
||||
use App\Models\Server;
|
||||
use App\Models\SslCertificate;
|
||||
use App\Models\StandaloneKeydb;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Attributes\Validate;
|
||||
@@ -53,12 +56,20 @@ class General extends Component
|
||||
#[Validate(['nullable', 'boolean'])]
|
||||
public bool $isLogDrainEnabled = false;
|
||||
|
||||
public ?Carbon $certificateValidUntil = null;
|
||||
|
||||
#[Validate(['boolean'])]
|
||||
public bool $enable_ssl = false;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$userId = Auth::id();
|
||||
$teamId = Auth::user()->currentTeam()->id;
|
||||
|
||||
return [
|
||||
"echo-private:team.{$teamId},DatabaseProxyStopped" => 'databaseProxyStopped',
|
||||
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
|
||||
'refresh' => '$refresh',
|
||||
];
|
||||
}
|
||||
|
||||
@@ -67,6 +78,12 @@ class General extends Component
|
||||
try {
|
||||
$this->syncData();
|
||||
$this->server = data_get($this->database, 'destination.server');
|
||||
|
||||
$existingCert = $this->database->sslCertificates()->first();
|
||||
|
||||
if ($existingCert) {
|
||||
$this->certificateValidUntil = $existingCert->valid_until;
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
@@ -86,6 +103,7 @@ class General extends Component
|
||||
$this->database->public_port = $this->publicPort;
|
||||
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
|
||||
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
|
||||
$this->database->enable_ssl = $this->enable_ssl;
|
||||
$this->database->save();
|
||||
|
||||
$this->dbUrl = $this->database->internal_db_url;
|
||||
@@ -101,6 +119,7 @@ class General extends Component
|
||||
$this->publicPort = $this->database->public_port;
|
||||
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
|
||||
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
|
||||
$this->enable_ssl = $this->database->enable_ssl;
|
||||
$this->dbUrl = $this->database->internal_db_url;
|
||||
$this->dbUrlPublic = $this->database->external_db_url;
|
||||
}
|
||||
@@ -179,4 +198,48 @@ class General extends Component
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveSSL()
|
||||
{
|
||||
try {
|
||||
$this->syncData(true);
|
||||
$this->dispatch('success', 'SSL configuration updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function regenerateSslCertificate()
|
||||
{
|
||||
try {
|
||||
$existingCert = $this->database->sslCertificates()->first();
|
||||
|
||||
if (! $existingCert) {
|
||||
$this->dispatch('error', 'No existing SSL certificate found for this database.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$caCert = SslCertificate::where('server_id', $existingCert->server_id)
|
||||
->where('is_ca_certificate', true)
|
||||
->first();
|
||||
|
||||
SslHelper::generateSslCertificate(
|
||||
commonName: $existingCert->commonName,
|
||||
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
|
||||
resourceType: $existingCert->resource_type,
|
||||
resourceId: $existingCert->resource_id,
|
||||
serverId: $existingCert->server_id,
|
||||
caCert: $caCert->ssl_certificate,
|
||||
caKey: $caCert->ssl_private_key,
|
||||
configurationDir: $existingCert->configuration_dir,
|
||||
mountPath: $existingCert->mount_path,
|
||||
isPemKeyFileRequired: true,
|
||||
);
|
||||
|
||||
$this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.');
|
||||
} catch (Exception $e) {
|
||||
handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,9 +4,13 @@ namespace App\Livewire\Project\Database\Mariadb;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Helpers\SslHelper;
|
||||
use App\Models\Server;
|
||||
use App\Models\SslCertificate;
|
||||
use App\Models\StandaloneMariadb;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Component;
|
||||
|
||||
class General extends Component
|
||||
@@ -21,6 +25,18 @@ class General extends Component
|
||||
|
||||
public ?string $db_url_public = null;
|
||||
|
||||
public ?Carbon $certificateValidUntil = null;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$userId = Auth::id();
|
||||
|
||||
return [
|
||||
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
|
||||
'refresh' => '$refresh',
|
||||
];
|
||||
}
|
||||
|
||||
protected $rules = [
|
||||
'database.name' => 'required',
|
||||
'database.description' => 'nullable',
|
||||
@@ -35,6 +51,7 @@ class General extends Component
|
||||
'database.public_port' => 'nullable|integer',
|
||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||
'database.custom_docker_run_options' => 'nullable',
|
||||
'database.enable_ssl' => 'boolean',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@@ -50,6 +67,7 @@ class General extends Component
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
'database.custom_docker_run_options' => 'Custom Docker Options',
|
||||
'database.enable_ssl' => 'Enable SSL',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@@ -57,6 +75,12 @@ class General extends Component
|
||||
$this->db_url = $this->database->internal_db_url;
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->server = data_get($this->database, 'destination.server');
|
||||
|
||||
$existingCert = $this->database->sslCertificates()->first();
|
||||
|
||||
if ($existingCert) {
|
||||
$this->certificateValidUntil = $existingCert->valid_until;
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveAdvanced()
|
||||
@@ -127,6 +151,48 @@ class General extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveSSL()
|
||||
{
|
||||
try {
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'SSL configuration updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function regenerateSslCertificate()
|
||||
{
|
||||
try {
|
||||
$existingCert = $this->database->sslCertificates()->first();
|
||||
|
||||
if (! $existingCert) {
|
||||
$this->dispatch('error', 'No existing SSL certificate found for this database.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first();
|
||||
|
||||
SslHelper::generateSslCertificate(
|
||||
commonName: $existingCert->common_name,
|
||||
subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
|
||||
resourceType: $existingCert->resource_type,
|
||||
resourceId: $existingCert->resource_id,
|
||||
serverId: $existingCert->server_id,
|
||||
caCert: $caCert->ssl_certificate,
|
||||
caKey: $caCert->ssl_private_key,
|
||||
configurationDir: $existingCert->configuration_dir,
|
||||
mountPath: $existingCert->mount_path,
|
||||
isPemKeyFileRequired: true,
|
||||
);
|
||||
|
||||
$this->dispatch('success', 'SSL certificates have been regenerated. Please restart the database for changes to take effect.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function refresh(): void
|
||||
{
|
||||
$this->database->refresh();
|
||||
|
||||
@@ -4,9 +4,13 @@ namespace App\Livewire\Project\Database\Mongodb;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Helpers\SslHelper;
|
||||
use App\Models\Server;
|
||||
use App\Models\SslCertificate;
|
||||
use App\Models\StandaloneMongodb;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Component;
|
||||
|
||||
class General extends Component
|
||||
@@ -21,6 +25,18 @@ class General extends Component
|
||||
|
||||
public ?string $db_url_public = null;
|
||||
|
||||
public ?Carbon $certificateValidUntil = null;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$userId = Auth::id();
|
||||
|
||||
return [
|
||||
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
|
||||
'refresh' => '$refresh',
|
||||
];
|
||||
}
|
||||
|
||||
protected $rules = [
|
||||
'database.name' => 'required',
|
||||
'database.description' => 'nullable',
|
||||
@@ -34,6 +50,8 @@ class General extends Component
|
||||
'database.public_port' => 'nullable|integer',
|
||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||
'database.custom_docker_run_options' => 'nullable',
|
||||
'database.enable_ssl' => 'boolean',
|
||||
'database.ssl_mode' => 'nullable|string|in:allow,prefer,require,verify-full',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@@ -48,6 +66,8 @@ class General extends Component
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||
'database.enable_ssl' => 'Enable SSL',
|
||||
'database.ssl_mode' => 'SSL Mode',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@@ -55,6 +75,12 @@ class General extends Component
|
||||
$this->db_url = $this->database->internal_db_url;
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->server = data_get($this->database, 'destination.server');
|
||||
|
||||
$existingCert = $this->database->sslCertificates()->first();
|
||||
|
||||
if ($existingCert) {
|
||||
$this->certificateValidUntil = $existingCert->valid_until;
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveAdvanced()
|
||||
@@ -128,6 +154,53 @@ class General extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedDatabaseSslMode()
|
||||
{
|
||||
$this->instantSaveSSL();
|
||||
}
|
||||
|
||||
public function instantSaveSSL()
|
||||
{
|
||||
try {
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'SSL configuration updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function regenerateSslCertificate()
|
||||
{
|
||||
try {
|
||||
$existingCert = $this->database->sslCertificates()->first();
|
||||
|
||||
if (! $existingCert) {
|
||||
$this->dispatch('error', 'No existing SSL certificate found for this database.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first();
|
||||
|
||||
SslHelper::generateSslCertificate(
|
||||
commonName: $existingCert->common_name,
|
||||
subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
|
||||
resourceType: $existingCert->resource_type,
|
||||
resourceId: $existingCert->resource_id,
|
||||
serverId: $existingCert->server_id,
|
||||
caCert: $caCert->ssl_certificate,
|
||||
caKey: $caCert->ssl_private_key,
|
||||
configurationDir: $existingCert->configuration_dir,
|
||||
mountPath: $existingCert->mount_path,
|
||||
isPemKeyFileRequired: true,
|
||||
);
|
||||
|
||||
$this->dispatch('success', 'SSL certificates have been regenerated. Please restart the database for changes to take effect.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function refresh(): void
|
||||
{
|
||||
$this->database->refresh();
|
||||
|
||||
@@ -4,9 +4,13 @@ namespace App\Livewire\Project\Database\Mysql;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Helpers\SslHelper;
|
||||
use App\Models\Server;
|
||||
use App\Models\SslCertificate;
|
||||
use App\Models\StandaloneMysql;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Component;
|
||||
|
||||
class General extends Component
|
||||
@@ -21,6 +25,18 @@ class General extends Component
|
||||
|
||||
public ?string $db_url_public = null;
|
||||
|
||||
public ?Carbon $certificateValidUntil = null;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$userId = Auth::id();
|
||||
|
||||
return [
|
||||
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
|
||||
'refresh' => '$refresh',
|
||||
];
|
||||
}
|
||||
|
||||
protected $rules = [
|
||||
'database.name' => 'required',
|
||||
'database.description' => 'nullable',
|
||||
@@ -35,6 +51,8 @@ class General extends Component
|
||||
'database.public_port' => 'nullable|integer',
|
||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||
'database.custom_docker_run_options' => 'nullable',
|
||||
'database.enable_ssl' => 'boolean',
|
||||
'database.ssl_mode' => 'nullable|string|in:PREFERRED,REQUIRED,VERIFY_CA,VERIFY_IDENTITY',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@@ -50,6 +68,8 @@ class General extends Component
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||
'database.enable_ssl' => 'Enable SSL',
|
||||
'database.ssl_mode' => 'SSL Mode',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@@ -57,6 +77,12 @@ class General extends Component
|
||||
$this->db_url = $this->database->internal_db_url;
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->server = data_get($this->database, 'destination.server');
|
||||
|
||||
$existingCert = $this->database->sslCertificates()->first();
|
||||
|
||||
if ($existingCert) {
|
||||
$this->certificateValidUntil = $existingCert->valid_until;
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveAdvanced()
|
||||
@@ -127,6 +153,53 @@ class General extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedDatabaseSslMode()
|
||||
{
|
||||
$this->instantSaveSSL();
|
||||
}
|
||||
|
||||
public function instantSaveSSL()
|
||||
{
|
||||
try {
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'SSL configuration updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function regenerateSslCertificate()
|
||||
{
|
||||
try {
|
||||
$existingCert = $this->database->sslCertificates()->first();
|
||||
|
||||
if (! $existingCert) {
|
||||
$this->dispatch('error', 'No existing SSL certificate found for this database.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first();
|
||||
|
||||
SslHelper::generateSslCertificate(
|
||||
commonName: $existingCert->common_name,
|
||||
subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
|
||||
resourceType: $existingCert->resource_type,
|
||||
resourceId: $existingCert->resource_id,
|
||||
serverId: $existingCert->server_id,
|
||||
caCert: $caCert->ssl_certificate,
|
||||
caKey: $caCert->ssl_private_key,
|
||||
configurationDir: $existingCert->configuration_dir,
|
||||
mountPath: $existingCert->mount_path,
|
||||
isPemKeyFileRequired: true,
|
||||
);
|
||||
|
||||
$this->dispatch('success', 'SSL certificates have been regenerated. Please restart the database for changes to take effect.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function refresh(): void
|
||||
{
|
||||
$this->database->refresh();
|
||||
|
||||
@@ -4,9 +4,13 @@ namespace App\Livewire\Project\Database\Postgresql;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Helpers\SslHelper;
|
||||
use App\Models\Server;
|
||||
use App\Models\SslCertificate;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Component;
|
||||
|
||||
class General extends Component
|
||||
@@ -23,10 +27,15 @@ class General extends Component
|
||||
|
||||
public ?string $db_url_public = null;
|
||||
|
||||
public ?Carbon $certificateValidUntil = null;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$userId = Auth::id();
|
||||
|
||||
return [
|
||||
'refresh',
|
||||
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
|
||||
'refresh' => '$refresh',
|
||||
'save_init_script',
|
||||
'delete_init_script',
|
||||
];
|
||||
@@ -48,6 +57,8 @@ class General extends Component
|
||||
'database.public_port' => 'nullable|integer',
|
||||
'database.is_log_drain_enabled' => 'nullable|boolean',
|
||||
'database.custom_docker_run_options' => 'nullable',
|
||||
'database.enable_ssl' => 'boolean',
|
||||
'database.ssl_mode' => 'nullable|string|in:allow,prefer,require,verify-ca,verify-full',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@@ -65,6 +76,8 @@ class General extends Component
|
||||
'database.is_public' => 'Is Public',
|
||||
'database.public_port' => 'Public Port',
|
||||
'database.custom_docker_run_options' => 'Custom Docker Run Options',
|
||||
'database.enable_ssl' => 'Enable SSL',
|
||||
'database.ssl_mode' => 'SSL Mode',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@@ -72,6 +85,12 @@ class General extends Component
|
||||
$this->db_url = $this->database->internal_db_url;
|
||||
$this->db_url_public = $this->database->external_db_url;
|
||||
$this->server = data_get($this->database, 'destination.server');
|
||||
|
||||
$existingCert = $this->database->sslCertificates()->first();
|
||||
|
||||
if ($existingCert) {
|
||||
$this->certificateValidUntil = $existingCert->valid_until;
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveAdvanced()
|
||||
@@ -91,6 +110,53 @@ class General extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function updatedDatabaseSslMode()
|
||||
{
|
||||
$this->instantSaveSSL();
|
||||
}
|
||||
|
||||
public function instantSaveSSL()
|
||||
{
|
||||
try {
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'SSL configuration updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function regenerateSslCertificate()
|
||||
{
|
||||
try {
|
||||
$existingCert = $this->database->sslCertificates()->first();
|
||||
|
||||
if (! $existingCert) {
|
||||
$this->dispatch('error', 'No existing SSL certificate found for this database.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first();
|
||||
|
||||
SslHelper::generateSslCertificate(
|
||||
commonName: $existingCert->common_name,
|
||||
subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
|
||||
resourceType: $existingCert->resource_type,
|
||||
resourceId: $existingCert->resource_id,
|
||||
serverId: $existingCert->server_id,
|
||||
caCert: $caCert->ssl_certificate,
|
||||
caKey: $caCert->ssl_private_key,
|
||||
configurationDir: $existingCert->configuration_dir,
|
||||
mountPath: $existingCert->mount_path,
|
||||
isPemKeyFileRequired: true,
|
||||
);
|
||||
|
||||
$this->dispatch('success', 'SSL certificates have been regenerated. Please restart the database for changes to take effect.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSave()
|
||||
{
|
||||
try {
|
||||
@@ -143,7 +209,7 @@ class General extends Component
|
||||
$delete_command = "rm -f $old_file_path";
|
||||
try {
|
||||
instant_remote_process([$delete_command], $this->server);
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
$this->dispatch('error', 'Failed to remove old init script from server: '.$e->getMessage());
|
||||
|
||||
return;
|
||||
@@ -184,7 +250,7 @@ class General extends Component
|
||||
$command = "rm -f $file_path";
|
||||
try {
|
||||
instant_remote_process([$command], $this->server);
|
||||
} catch (\Exception $e) {
|
||||
} catch (Exception $e) {
|
||||
$this->dispatch('error', 'Failed to remove init script from server: '.$e->getMessage());
|
||||
|
||||
return;
|
||||
@@ -201,16 +267,11 @@ class General extends Component
|
||||
|
||||
$this->database->init_scripts = $updatedScripts;
|
||||
$this->database->save();
|
||||
$this->refresh();
|
||||
$this->dispatch('refresh')->self();
|
||||
$this->dispatch('success', 'Init script deleted from the database and the server.');
|
||||
}
|
||||
}
|
||||
|
||||
public function refresh(): void
|
||||
{
|
||||
$this->database->refresh();
|
||||
}
|
||||
|
||||
public function save_new_init_script()
|
||||
{
|
||||
$this->validate([
|
||||
|
||||
@@ -4,25 +4,24 @@ namespace App\Livewire\Project\Database\Redis;
|
||||
|
||||
use App\Actions\Database\StartDatabaseProxy;
|
||||
use App\Actions\Database\StopDatabaseProxy;
|
||||
use App\Helpers\SslHelper;
|
||||
use App\Models\Server;
|
||||
use App\Models\SslCertificate;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Component;
|
||||
|
||||
class General extends Component
|
||||
{
|
||||
protected $listeners = [
|
||||
'envsUpdated' => 'refresh',
|
||||
'refresh',
|
||||
];
|
||||
|
||||
public Server $server;
|
||||
|
||||
public StandaloneRedis $database;
|
||||
|
||||
public string $redis_username;
|
||||
|
||||
public string $redis_password;
|
||||
public ?string $redis_password;
|
||||
|
||||
public string $redis_version;
|
||||
|
||||
@@ -30,6 +29,19 @@ class General extends Component
|
||||
|
||||
public ?string $db_url_public = null;
|
||||
|
||||
public ?Carbon $certificateValidUntil = null;
|
||||
|
||||
public function getListeners()
|
||||
{
|
||||
$userId = Auth::id();
|
||||
|
||||
return [
|
||||
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
|
||||
'envsUpdated' => 'refresh',
|
||||
'refresh',
|
||||
];
|
||||
}
|
||||
|
||||
protected $rules = [
|
||||
'database.name' => 'required',
|
||||
'database.description' => 'nullable',
|
||||
@@ -42,6 +54,7 @@ class General extends Component
|
||||
'database.custom_docker_run_options' => 'nullable',
|
||||
'redis_username' => 'required',
|
||||
'redis_password' => 'required',
|
||||
'database.enable_ssl' => 'boolean',
|
||||
];
|
||||
|
||||
protected $validationAttributes = [
|
||||
@@ -55,12 +68,18 @@ class General extends Component
|
||||
'database.custom_docker_run_options' => 'Custom Docker Options',
|
||||
'redis_username' => 'Redis Username',
|
||||
'redis_password' => 'Redis Password',
|
||||
'database.enable_ssl' => 'Enable SSL',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
{
|
||||
$this->server = data_get($this->database, 'destination.server');
|
||||
$this->refreshView();
|
||||
$existingCert = $this->database->sslCertificates()->first();
|
||||
|
||||
if ($existingCert) {
|
||||
$this->certificateValidUntil = $existingCert->valid_until;
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveAdvanced()
|
||||
@@ -136,6 +155,48 @@ class General extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function instantSaveSSL()
|
||||
{
|
||||
try {
|
||||
$this->database->save();
|
||||
$this->dispatch('success', 'SSL configuration updated.');
|
||||
} catch (Exception $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function regenerateSslCertificate()
|
||||
{
|
||||
try {
|
||||
$existingCert = $this->database->sslCertificates()->first();
|
||||
|
||||
if (! $existingCert) {
|
||||
$this->dispatch('error', 'No existing SSL certificate found for this database.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$caCert = SslCertificate::where('server_id', $existingCert->server_id)->where('is_ca_certificate', true)->first();
|
||||
|
||||
SslHelper::generateSslCertificate(
|
||||
commonName: $existingCert->commonName,
|
||||
subjectAlternativeNames: $existingCert->subjectAlternativeNames ?? [],
|
||||
resourceType: $existingCert->resource_type,
|
||||
resourceId: $existingCert->resource_id,
|
||||
serverId: $existingCert->server_id,
|
||||
caCert: $caCert->ssl_certificate,
|
||||
caKey: $caCert->ssl_private_key,
|
||||
configurationDir: $existingCert->configuration_dir,
|
||||
mountPath: $existingCert->mount_path,
|
||||
isPemKeyFileRequired: true,
|
||||
);
|
||||
|
||||
$this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.');
|
||||
} catch (Exception $e) {
|
||||
handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function refresh(): void
|
||||
{
|
||||
$this->database->refresh();
|
||||
|
||||
@@ -7,7 +7,6 @@ use App\Models\Project;
|
||||
use App\Models\Service;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\SwarmDocker;
|
||||
use Illuminate\Support\Str;
|
||||
use Livewire\Component;
|
||||
use Symfony\Component\Yaml\Yaml;
|
||||
|
||||
@@ -66,7 +65,6 @@ class DockerCompose extends Component
|
||||
$destination_class = $destination->getMorphClass();
|
||||
|
||||
$service = Service::create([
|
||||
'name' => 'service'.Str::random(10),
|
||||
'docker_compose_raw' => $this->dockerComposeRaw,
|
||||
'environment_id' => $environment->id,
|
||||
'server_id' => (int) $server_id,
|
||||
@@ -85,8 +83,6 @@ class DockerCompose extends Component
|
||||
'resourceable_type' => $service->getMorphClass(),
|
||||
]);
|
||||
}
|
||||
$service->name = "service-$service->uuid";
|
||||
|
||||
$service->parse(isNew: true);
|
||||
|
||||
return redirect()->route('project.service.configuration', [
|
||||
|
||||
@@ -106,11 +106,15 @@ class GithubPrivateRepository extends Component
|
||||
$this->selected_github_app_id = $github_app_id;
|
||||
$this->github_app = GithubApp::where('id', $github_app_id)->first();
|
||||
$this->token = generateGithubInstallationToken($this->github_app);
|
||||
$this->loadRepositoryByPage();
|
||||
$repositories = loadRepositoryByPage($this->github_app, $this->token, $this->page);
|
||||
$this->total_repositories_count = $repositories['total_count'];
|
||||
$this->repositories = $this->repositories->concat(collect($repositories['repositories']));
|
||||
if ($this->repositories->count() < $this->total_repositories_count) {
|
||||
while ($this->repositories->count() < $this->total_repositories_count) {
|
||||
$this->page++;
|
||||
$this->loadRepositoryByPage();
|
||||
$repositories = loadRepositoryByPage($this->github_app, $this->token, $this->page);
|
||||
$this->total_repositories_count = $repositories['total_count'];
|
||||
$this->repositories = $this->repositories->concat(collect($repositories['repositories']));
|
||||
}
|
||||
}
|
||||
$this->repositories = $this->repositories->sortBy('name');
|
||||
@@ -120,21 +124,6 @@ class GithubPrivateRepository extends Component
|
||||
$this->current_step = 'repository';
|
||||
}
|
||||
|
||||
protected function loadRepositoryByPage()
|
||||
{
|
||||
$response = Http::withToken($this->token)->get("{$this->github_app->api_url}/installation/repositories?per_page=100&page={$this->page}");
|
||||
$json = $response->json();
|
||||
if ($response->status() !== 200) {
|
||||
return $this->dispatch('error', $json['message']);
|
||||
}
|
||||
|
||||
if ($json['total_count'] === 0) {
|
||||
return;
|
||||
}
|
||||
$this->total_repositories_count = $json['total_count'];
|
||||
$this->repositories = $this->repositories->concat(collect($json['repositories']));
|
||||
}
|
||||
|
||||
public function loadBranches()
|
||||
{
|
||||
$this->selected_repository_owner = $this->repositories->where('id', $this->selected_repository_id)->first()['owner']['login'];
|
||||
|
||||
@@ -74,7 +74,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
||||
'fqdn' => $fqdn,
|
||||
]);
|
||||
|
||||
$application->parseHealthcheckFromDockerfile(dockerfile: collect(str($this->dockerfile)->trim()->explode("\n")), isInit: true);
|
||||
$application->parseHealthcheckFromDockerfile(dockerfile: $this->dockerfile, isInit: true);
|
||||
|
||||
return redirect()->route('project.application.configuration', [
|
||||
'application_uuid' => $application->uuid,
|
||||
|
||||
@@ -73,7 +73,6 @@ class Create extends Component
|
||||
if ($oneClickService) {
|
||||
$destination = StandaloneDocker::whereUuid($destination_uuid)->first();
|
||||
$service_payload = [
|
||||
'name' => "$oneClickServiceName-".str()->random(10),
|
||||
'docker_compose_raw' => base64_decode($oneClickService),
|
||||
'environment_id' => $environment->id,
|
||||
'service_type' => $oneClickServiceName,
|
||||
|
||||
@@ -49,7 +49,6 @@ class FileStorage extends Component
|
||||
$this->workdir = null;
|
||||
$this->fs_path = $this->fileStorage->fs_path;
|
||||
}
|
||||
$this->fileStorage->loadStorageOnServer();
|
||||
}
|
||||
|
||||
public function convertToDirectory()
|
||||
@@ -68,6 +67,18 @@ class FileStorage extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function loadStorageOnServer()
|
||||
{
|
||||
try {
|
||||
$this->fileStorage->loadStorageOnServer();
|
||||
$this->dispatch('success', 'File storage loaded from server.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->dispatch('refreshStorages');
|
||||
}
|
||||
}
|
||||
|
||||
public function convertToFile()
|
||||
{
|
||||
try {
|
||||
|
||||
@@ -3,10 +3,13 @@
|
||||
namespace App\Livewire\Project\Shared\EnvironmentVariable;
|
||||
|
||||
use App\Models\EnvironmentVariable;
|
||||
use App\Traits\EnvironmentVariableProtection;
|
||||
use Livewire\Component;
|
||||
|
||||
class All extends Component
|
||||
{
|
||||
use EnvironmentVariableProtection;
|
||||
|
||||
public $resource;
|
||||
|
||||
public string $resourceClass;
|
||||
@@ -138,17 +141,57 @@ class All extends Component
|
||||
private function handleBulkSubmit()
|
||||
{
|
||||
$variables = parseEnvFormatToArray($this->variables);
|
||||
$changesMade = false;
|
||||
$errorOccurred = false;
|
||||
|
||||
$this->deleteRemovedVariables(false, $variables);
|
||||
$this->updateOrCreateVariables(false, $variables);
|
||||
// Try to delete removed variables
|
||||
$deletedCount = $this->deleteRemovedVariables(false, $variables);
|
||||
if ($deletedCount > 0) {
|
||||
$changesMade = true;
|
||||
} elseif ($deletedCount === 0 && $this->resource->environment_variables()->whereNotIn('key', array_keys($variables))->exists()) {
|
||||
// If we tried to delete but couldn't (due to Docker Compose), mark as error
|
||||
$errorOccurred = true;
|
||||
}
|
||||
|
||||
// Update or create variables
|
||||
$updatedCount = $this->updateOrCreateVariables(false, $variables);
|
||||
if ($updatedCount > 0) {
|
||||
$changesMade = true;
|
||||
}
|
||||
|
||||
if ($this->showPreview) {
|
||||
$previewVariables = parseEnvFormatToArray($this->variablesPreview);
|
||||
$this->deleteRemovedVariables(true, $previewVariables);
|
||||
$this->updateOrCreateVariables(true, $previewVariables);
|
||||
|
||||
// Try to delete removed preview variables
|
||||
$deletedPreviewCount = $this->deleteRemovedVariables(true, $previewVariables);
|
||||
if ($deletedPreviewCount > 0) {
|
||||
$changesMade = true;
|
||||
} elseif ($deletedPreviewCount === 0 && $this->resource->environment_variables_preview()->whereNotIn('key', array_keys($previewVariables))->exists()) {
|
||||
// If we tried to delete but couldn't (due to Docker Compose), mark as error
|
||||
$errorOccurred = true;
|
||||
}
|
||||
|
||||
// Update or create preview variables
|
||||
$updatedPreviewCount = $this->updateOrCreateVariables(true, $previewVariables);
|
||||
if ($updatedPreviewCount > 0) {
|
||||
$changesMade = true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->dispatch('success', 'Environment variables updated.');
|
||||
// Debug information
|
||||
\Log::info('Environment variables update status', [
|
||||
'deletedCount' => $deletedCount,
|
||||
'updatedCount' => $updatedCount,
|
||||
'deletedPreviewCount' => $deletedPreviewCount ?? 0,
|
||||
'updatedPreviewCount' => $updatedPreviewCount ?? 0,
|
||||
'changesMade' => $changesMade,
|
||||
'errorOccurred' => $errorOccurred,
|
||||
]);
|
||||
|
||||
// Only show success message if changes were actually made and no errors occurred
|
||||
if ($changesMade && ! $errorOccurred) {
|
||||
$this->dispatch('success', 'Environment variables updated.');
|
||||
}
|
||||
}
|
||||
|
||||
private function handleSingleSubmit($data)
|
||||
@@ -184,11 +227,46 @@ class All extends Component
|
||||
private function deleteRemovedVariables($isPreview, $variables)
|
||||
{
|
||||
$method = $isPreview ? 'environment_variables_preview' : 'environment_variables';
|
||||
|
||||
// Get all environment variables that will be deleted
|
||||
$variablesToDelete = $this->resource->$method()->whereNotIn('key', array_keys($variables))->get();
|
||||
|
||||
// If there are no variables to delete, return 0
|
||||
if ($variablesToDelete->isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Check for system variables that shouldn't be deleted
|
||||
foreach ($variablesToDelete as $envVar) {
|
||||
if ($this->isProtectedEnvironmentVariable($envVar->key)) {
|
||||
$this->dispatch('error', "Cannot delete system environment variable '{$envVar->key}'.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if any of these variables are used in Docker Compose
|
||||
if ($this->resource->type() === 'service' || $this->resource->build_pack === 'dockercompose') {
|
||||
foreach ($variablesToDelete as $envVar) {
|
||||
[$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($envVar->key, $this->resource->docker_compose);
|
||||
|
||||
if ($isUsed) {
|
||||
$this->dispatch('error', "Cannot delete environment variable '{$envVar->key}' <br><br>Please remove it from the Docker Compose file first.");
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we get here, no variables are used in Docker Compose, so we can delete them
|
||||
$this->resource->$method()->whereNotIn('key', array_keys($variables))->delete();
|
||||
|
||||
return $variablesToDelete->count();
|
||||
}
|
||||
|
||||
private function updateOrCreateVariables($isPreview, $variables)
|
||||
{
|
||||
$count = 0;
|
||||
foreach ($variables as $key => $value) {
|
||||
if (str($key)->startsWith('SERVICE_FQDN') || str($key)->startsWith('SERVICE_URL')) {
|
||||
continue;
|
||||
@@ -198,8 +276,12 @@ class All extends Component
|
||||
|
||||
if ($found) {
|
||||
if (! $found->is_shown_once && ! $found->is_multiline) {
|
||||
$found->value = $value;
|
||||
$found->save();
|
||||
// Only count as a change if the value actually changed
|
||||
if ($found->value !== $value) {
|
||||
$found->value = $value;
|
||||
$found->save();
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$environment = new EnvironmentVariable;
|
||||
@@ -212,8 +294,11 @@ class All extends Component
|
||||
$environment->resourceable_type = $this->resource->getMorphClass();
|
||||
|
||||
$environment->save();
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function refreshEnvs()
|
||||
|
||||
@@ -4,10 +4,13 @@ namespace App\Livewire\Project\Shared\EnvironmentVariable;
|
||||
|
||||
use App\Models\EnvironmentVariable as ModelsEnvironmentVariable;
|
||||
use App\Models\SharedEnvironmentVariable;
|
||||
use App\Traits\EnvironmentVariableProtection;
|
||||
use Livewire\Component;
|
||||
|
||||
class Show extends Component
|
||||
{
|
||||
use EnvironmentVariableProtection;
|
||||
|
||||
public $parameters;
|
||||
|
||||
public ModelsEnvironmentVariable|SharedEnvironmentVariable $env;
|
||||
@@ -40,6 +43,8 @@ class Show extends Component
|
||||
|
||||
public bool $is_really_required = false;
|
||||
|
||||
public bool $is_redis_credential = false;
|
||||
|
||||
protected $listeners = [
|
||||
'refreshEnvs' => 'refresh',
|
||||
'refresh',
|
||||
@@ -65,7 +70,9 @@ class Show extends Component
|
||||
}
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->checkEnvs();
|
||||
|
||||
if ($this->type === 'standalone-redis' && ($this->env->key === 'REDIS_PASSWORD' || $this->env->key === 'REDIS_USERNAME')) {
|
||||
$this->is_redis_credential = true;
|
||||
}
|
||||
}
|
||||
|
||||
public function refresh()
|
||||
@@ -171,6 +178,24 @@ class Show extends Component
|
||||
public function delete()
|
||||
{
|
||||
try {
|
||||
// Check if the variable is protected
|
||||
if ($this->isProtectedEnvironmentVariable($this->env->key)) {
|
||||
$this->dispatch('error', "Cannot delete system environment variable '{$this->env->key}'.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the variable is used in Docker Compose
|
||||
if ($this->type === 'service' || $this->type === 'application' && $this->env->resource()?->docker_compose) {
|
||||
[$isUsed, $reason] = $this->isEnvironmentVariableUsedInDockerCompose($this->env->key, $this->env->resource()?->docker_compose);
|
||||
|
||||
if ($isUsed) {
|
||||
$this->dispatch('error', "Cannot delete environment variable '{$this->env->key}' <br><br>Please remove it from the Docker Compose file first.");
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->env->delete();
|
||||
$this->dispatch('environmentVariableDeleted');
|
||||
$this->dispatch('success', 'Environment variable deleted successfully.');
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
|
||||
namespace App\Livewire\Server;
|
||||
|
||||
use App\Helpers\SslHelper;
|
||||
use App\Jobs\RegenerateSslCertJob;
|
||||
use App\Models\Server;
|
||||
use App\Models\SslCertificate;
|
||||
use Carbon\Carbon;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
|
||||
@@ -10,6 +14,14 @@ class Advanced extends Component
|
||||
{
|
||||
public Server $server;
|
||||
|
||||
public ?SslCertificate $caCertificate = null;
|
||||
|
||||
public $showCertificate = false;
|
||||
|
||||
public $certificateContent = '';
|
||||
|
||||
public ?Carbon $certificateValidUntil = null;
|
||||
|
||||
public array $parameters = [];
|
||||
|
||||
#[Validate(['string'])]
|
||||
@@ -30,11 +42,99 @@ class Advanced extends Component
|
||||
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
|
||||
$this->parameters = get_route_parameters();
|
||||
$this->syncData();
|
||||
$this->loadCaCertificate();
|
||||
} catch (\Throwable) {
|
||||
return redirect()->route('server.index');
|
||||
}
|
||||
}
|
||||
|
||||
public function loadCaCertificate()
|
||||
{
|
||||
$this->caCertificate = SslCertificate::where('server_id', $this->server->id)->where('is_ca_certificate', true)->first();
|
||||
|
||||
if ($this->caCertificate) {
|
||||
$this->certificateContent = $this->caCertificate->ssl_certificate;
|
||||
$this->certificateValidUntil = $this->caCertificate->valid_until;
|
||||
}
|
||||
}
|
||||
|
||||
public function toggleCertificate()
|
||||
{
|
||||
$this->showCertificate = ! $this->showCertificate;
|
||||
}
|
||||
|
||||
public function saveCaCertificate()
|
||||
{
|
||||
try {
|
||||
if (! $this->certificateContent) {
|
||||
throw new \Exception('Certificate content cannot be empty.');
|
||||
}
|
||||
|
||||
if (! openssl_x509_read($this->certificateContent)) {
|
||||
throw new \Exception('Invalid certificate format.');
|
||||
}
|
||||
|
||||
if ($this->caCertificate) {
|
||||
$this->caCertificate->ssl_certificate = $this->certificateContent;
|
||||
$this->caCertificate->save();
|
||||
|
||||
$this->loadCaCertificate();
|
||||
|
||||
$this->writeCertificateToServer();
|
||||
|
||||
dispatch(new RegenerateSslCertJob(
|
||||
server_id: $this->server->id,
|
||||
force_regeneration: true
|
||||
));
|
||||
}
|
||||
$this->dispatch('success', 'CA Certificate saved successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function regenerateCaCertificate()
|
||||
{
|
||||
try {
|
||||
SslHelper::generateSslCertificate(
|
||||
commonName: 'Coolify CA Certificate',
|
||||
serverId: $this->server->id,
|
||||
isCaCertificate: true,
|
||||
validityDays: 10 * 365
|
||||
);
|
||||
|
||||
$this->loadCaCertificate();
|
||||
|
||||
$this->writeCertificateToServer();
|
||||
|
||||
dispatch(new RegenerateSslCertJob(
|
||||
server_id: $this->server->id,
|
||||
force_regeneration: true
|
||||
));
|
||||
|
||||
$this->loadCaCertificate();
|
||||
$this->dispatch('success', 'CA Certificate regenerated successfully.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
private function writeCertificateToServer()
|
||||
{
|
||||
$caCertPath = config('constants.coolify.base_config_path').'/ssl/';
|
||||
|
||||
$commands = collect([
|
||||
"mkdir -p $caCertPath",
|
||||
"chown -R 9999:root $caCertPath",
|
||||
"chmod -R 700 $caCertPath",
|
||||
"rm -rf $caCertPath/coolify-ca.crt",
|
||||
"echo '{$this->certificateContent}' > $caCertPath/coolify-ca.crt",
|
||||
"chmod 644 $caCertPath/coolify-ca.crt",
|
||||
]);
|
||||
|
||||
remote_process($commands, $this->server);
|
||||
}
|
||||
|
||||
public function syncData(bool $toModel = false)
|
||||
{
|
||||
if ($toModel) {
|
||||
|
||||
@@ -4,11 +4,10 @@ namespace App\Livewire\Server\Proxy;
|
||||
|
||||
use App\Actions\Proxy\CheckProxy;
|
||||
use App\Actions\Proxy\StartProxy;
|
||||
use App\Actions\Proxy\StopProxy;
|
||||
use App\Events\ProxyStatusChanged;
|
||||
use App\Jobs\RestartProxyJob;
|
||||
use App\Models\Server;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Process\InvokedProcess;
|
||||
use Illuminate\Support\Facades\Process;
|
||||
use Livewire\Component;
|
||||
|
||||
class Deploy extends Component
|
||||
@@ -65,7 +64,7 @@ class Deploy extends Component
|
||||
public function restart()
|
||||
{
|
||||
try {
|
||||
$this->stop();
|
||||
RestartProxyJob::dispatch($this->server);
|
||||
$this->dispatch('checkProxy');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
@@ -98,43 +97,10 @@ class Deploy extends Component
|
||||
public function stop(bool $forceStop = true)
|
||||
{
|
||||
try {
|
||||
$containerName = $this->server->isSwarm() ? 'coolify-proxy_traefik' : 'coolify-proxy';
|
||||
$timeout = 30;
|
||||
|
||||
$process = $this->stopContainer($containerName, $timeout);
|
||||
|
||||
$startTime = Carbon::now()->getTimestamp();
|
||||
while ($process->running()) {
|
||||
if (Carbon::now()->getTimestamp() - $startTime >= $timeout) {
|
||||
$this->forceStopContainer($containerName);
|
||||
break;
|
||||
}
|
||||
usleep(100000);
|
||||
}
|
||||
|
||||
$this->removeContainer($containerName);
|
||||
StopProxy::run($this->server, $forceStop);
|
||||
$this->dispatch('proxyStatusUpdated');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
} finally {
|
||||
$this->server->proxy->force_stop = $forceStop;
|
||||
$this->server->proxy->status = 'exited';
|
||||
$this->server->save();
|
||||
$this->dispatch('proxyStatusUpdated');
|
||||
}
|
||||
}
|
||||
|
||||
private function stopContainer(string $containerName, int $timeout): InvokedProcess
|
||||
{
|
||||
return Process::timeout($timeout)->start("docker stop --time=$timeout $containerName");
|
||||
}
|
||||
|
||||
private function forceStopContainer(string $containerName)
|
||||
{
|
||||
instant_remote_process(["docker kill $containerName"], $this->server, throwError: false);
|
||||
}
|
||||
|
||||
private function removeContainer(string $containerName)
|
||||
{
|
||||
instant_remote_process(["docker rm -f $containerName"], $this->server, throwError: false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@ class SettingsBackup extends Component
|
||||
{
|
||||
public InstanceSettings $settings;
|
||||
|
||||
public Server $server;
|
||||
|
||||
public ?StandalonePostgresql $database = null;
|
||||
|
||||
public ScheduledDatabaseBackup|null|array $backup = [];
|
||||
@@ -46,6 +48,7 @@ class SettingsBackup extends Component
|
||||
return redirect()->route('dashboard');
|
||||
} else {
|
||||
$settings = instanceSettings();
|
||||
$this->server = Server::findOrFail(0);
|
||||
$this->database = StandalonePostgresql::whereName('coolify-db')->first();
|
||||
$s3s = S3Storage::whereTeamId(0)->get() ?? [];
|
||||
if ($this->database) {
|
||||
@@ -60,6 +63,10 @@ class SettingsBackup extends Component
|
||||
$this->database->save();
|
||||
}
|
||||
$this->backup = $this->database->scheduledBackups->first();
|
||||
if ($this->backup && ! $this->server->isFunctional()) {
|
||||
$this->backup->enabled = false;
|
||||
$this->backup->save();
|
||||
}
|
||||
$this->executions = $this->backup->executions;
|
||||
}
|
||||
$this->settings = $settings;
|
||||
|
||||
@@ -4,7 +4,7 @@ namespace App\Livewire;
|
||||
|
||||
use App\Models\InstanceSettings;
|
||||
use App\Models\Team;
|
||||
use App\Notifications\Test;
|
||||
use App\Notifications\TransactionalEmails\Test;
|
||||
use Illuminate\Support\Facades\RateLimiter;
|
||||
use Livewire\Attributes\Locked;
|
||||
use Livewire\Attributes\Validate;
|
||||
@@ -225,7 +225,7 @@ class SettingsEmail extends Component
|
||||
'test-email:'.$this->team->id,
|
||||
$perMinute = 0,
|
||||
function () {
|
||||
$this->team?->notify(new Test($this->testEmailAddress, 'email'));
|
||||
$this->team?->notify(new Test($this->testEmailAddress));
|
||||
$this->dispatch('success', 'Test Email sent.');
|
||||
},
|
||||
$decaySeconds = 10,
|
||||
@@ -235,7 +235,7 @@ class SettingsEmail extends Component
|
||||
throw new \Exception('Too many messages sent!');
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e);
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user