Merge pull request #6859 from coollabsio/andrasbacsai/fix-livewire-field-reset

refactor: migrate database components from legacy model binding to explicit properties
This commit is contained in:
Andras Bacsai 2025-10-13 10:51:04 +02:00 committed by GitHub
commit bb9ddd089a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 803 additions and 461 deletions

View file

@ -58,7 +58,6 @@ public function getListeners()
return [
"echo-private:team.{$teamId},DatabaseProxyStopped" => 'databaseProxyStopped',
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
'refresh' => '$refresh',
];
}

View file

@ -18,12 +18,38 @@ class General extends Component
{
use AuthorizesRequests;
protected $listeners = ['refresh'];
public Server $server;
public StandaloneMariadb $database;
public string $name;
public ?string $description = null;
public string $mariadbRootPassword;
public string $mariadbUser;
public string $mariadbPassword;
public string $mariadbDatabase;
public ?string $mariadbConf = null;
public string $image;
public ?string $portsMappings = null;
public ?bool $isPublic = null;
public ?int $publicPort = null;
public bool $isLogDrainEnabled = false;
public ?string $customDockerRunOptions = null;
public bool $enableSsl = false;
public ?string $db_url = null;
public ?string $db_url_public = null;
@ -36,27 +62,26 @@ public function getListeners()
return [
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
'refresh' => '$refresh',
];
}
protected function rules(): array
{
return [
'database.name' => ValidationPatterns::nameRules(),
'database.description' => ValidationPatterns::descriptionRules(),
'database.mariadb_root_password' => 'required',
'database.mariadb_user' => 'required',
'database.mariadb_password' => 'required',
'database.mariadb_database' => 'required',
'database.mariadb_conf' => 'nullable',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
'database.enable_ssl' => 'boolean',
'name' => ValidationPatterns::nameRules(),
'description' => ValidationPatterns::descriptionRules(),
'mariadbRootPassword' => 'required',
'mariadbUser' => 'required',
'mariadbPassword' => 'required',
'mariadbDatabase' => 'required',
'mariadbConf' => 'nullable',
'image' => 'required',
'portsMappings' => 'nullable',
'isPublic' => 'nullable|boolean',
'publicPort' => 'nullable|integer',
'isLogDrainEnabled' => 'nullable|boolean',
'customDockerRunOptions' => 'nullable',
'enableSsl' => 'boolean',
];
}
@ -65,45 +90,90 @@ protected function messages(): array
return array_merge(
ValidationPatterns::combinedMessages(),
[
'database.name.required' => 'The Name field is required.',
'database.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
'database.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
'database.mariadb_root_password.required' => 'The Root Password field is required.',
'database.mariadb_user.required' => 'The MariaDB User field is required.',
'database.mariadb_password.required' => 'The MariaDB Password field is required.',
'database.mariadb_database.required' => 'The MariaDB Database field is required.',
'database.image.required' => 'The Docker Image field is required.',
'database.public_port.integer' => 'The Public Port must be an integer.',
'name.required' => 'The Name field is required.',
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
'mariadbRootPassword.required' => 'The Root Password field is required.',
'mariadbUser.required' => 'The MariaDB User field is required.',
'mariadbPassword.required' => 'The MariaDB Password field is required.',
'mariadbDatabase.required' => 'The MariaDB Database field is required.',
'image.required' => 'The Docker Image field is required.',
'publicPort.integer' => 'The Public Port must be an integer.',
]
);
}
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.mariadb_root_password' => 'Root Password',
'database.mariadb_user' => 'User',
'database.mariadb_password' => 'Password',
'database.mariadb_database' => 'Database',
'database.mariadb_conf' => 'MariaDB Configuration',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Options',
'database.enable_ssl' => 'Enable SSL',
'name' => 'Name',
'description' => 'Description',
'mariadbRootPassword' => 'Root Password',
'mariadbUser' => 'User',
'mariadbPassword' => 'Password',
'mariadbDatabase' => 'Database',
'mariadbConf' => 'MariaDB Configuration',
'image' => 'Image',
'portsMappings' => 'Port Mapping',
'isPublic' => 'Is Public',
'publicPort' => 'Public Port',
'customDockerRunOptions' => 'Custom Docker Options',
'enableSsl' => 'Enable SSL',
];
public function mount()
{
$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');
try {
$this->syncData();
$this->server = data_get($this->database, 'destination.server');
$existingCert = $this->database->sslCertificates()->first();
$existingCert = $this->database->sslCertificates()->first();
if ($existingCert) {
$this->certificateValidUntil = $existingCert->valid_until;
if ($existingCert) {
$this->certificateValidUntil = $existingCert->valid_until;
}
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->database->name = $this->name;
$this->database->description = $this->description;
$this->database->mariadb_root_password = $this->mariadbRootPassword;
$this->database->mariadb_user = $this->mariadbUser;
$this->database->mariadb_password = $this->mariadbPassword;
$this->database->mariadb_database = $this->mariadbDatabase;
$this->database->mariadb_conf = $this->mariadbConf;
$this->database->image = $this->image;
$this->database->ports_mappings = $this->portsMappings;
$this->database->is_public = $this->isPublic;
$this->database->public_port = $this->publicPort;
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
$this->database->enable_ssl = $this->enableSsl;
$this->database->save();
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
} else {
$this->name = $this->database->name;
$this->description = $this->database->description;
$this->mariadbRootPassword = $this->database->mariadb_root_password;
$this->mariadbUser = $this->database->mariadb_user;
$this->mariadbPassword = $this->database->mariadb_password;
$this->mariadbDatabase = $this->database->mariadb_database;
$this->mariadbConf = $this->database->mariadb_conf;
$this->image = $this->database->image;
$this->portsMappings = $this->database->ports_mappings;
$this->isPublic = $this->database->is_public;
$this->publicPort = $this->database->public_port;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->enableSsl = $this->database->enable_ssl;
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
}
}
@ -113,12 +183,12 @@ public function instantSaveAdvanced()
$this->authorize('update', $this->database);
if (! $this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->isLogDrainEnabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;
}
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Database updated.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
@ -131,11 +201,10 @@ public function submit()
try {
$this->authorize('update', $this->database);
if (str($this->database->public_port)->isEmpty()) {
$this->database->public_port = null;
if (str($this->publicPort)->isEmpty()) {
$this->publicPort = null;
}
$this->validate();
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Database updated.');
} catch (Exception $e) {
return handleError($e, $this);
@ -153,16 +222,16 @@ public function instantSave()
try {
$this->authorize('update', $this->database);
if ($this->database->is_public && ! $this->database->public_port) {
if ($this->isPublic && ! $this->publicPort) {
$this->dispatch('error', 'Public port is required.');
$this->database->is_public = false;
$this->isPublic = false;
return;
}
if ($this->database->is_public) {
if ($this->isPublic) {
if (! str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false;
$this->isPublic = false;
return;
}
@ -172,10 +241,9 @@ public function instantSave()
StopDatabaseProxy::run($this->database);
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
$this->db_url_public = $this->database->external_db_url;
$this->database->save();
$this->syncData(true);
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
$this->isPublic = ! $this->isPublic;
return handleError($e, $this);
}
@ -186,7 +254,7 @@ public function instantSaveSSL()
try {
$this->authorize('update', $this->database);
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
@ -230,6 +298,7 @@ public function regenerateSslCertificate()
public function refresh(): void
{
$this->database->refresh();
$this->syncData();
}
public function render()

View file

@ -18,12 +18,38 @@ class General extends Component
{
use AuthorizesRequests;
protected $listeners = ['refresh'];
public Server $server;
public StandaloneMongodb $database;
public string $name;
public ?string $description = null;
public ?string $mongoConf = null;
public string $mongoInitdbRootUsername;
public string $mongoInitdbRootPassword;
public string $mongoInitdbDatabase;
public string $image;
public ?string $portsMappings = null;
public ?bool $isPublic = null;
public ?int $publicPort = null;
public bool $isLogDrainEnabled = false;
public ?string $customDockerRunOptions = null;
public bool $enableSsl = false;
public ?string $sslMode = null;
public ?string $db_url = null;
public ?string $db_url_public = null;
@ -36,27 +62,26 @@ public function getListeners()
return [
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
'refresh' => '$refresh',
];
}
protected function rules(): array
{
return [
'database.name' => ValidationPatterns::nameRules(),
'database.description' => ValidationPatterns::descriptionRules(),
'database.mongo_conf' => 'nullable',
'database.mongo_initdb_root_username' => 'required',
'database.mongo_initdb_root_password' => 'required',
'database.mongo_initdb_database' => 'required',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
'database.enable_ssl' => 'boolean',
'database.ssl_mode' => 'nullable|string|in:allow,prefer,require,verify-full',
'name' => ValidationPatterns::nameRules(),
'description' => ValidationPatterns::descriptionRules(),
'mongoConf' => 'nullable',
'mongoInitdbRootUsername' => 'required',
'mongoInitdbRootPassword' => 'required',
'mongoInitdbDatabase' => 'required',
'image' => 'required',
'portsMappings' => 'nullable',
'isPublic' => 'nullable|boolean',
'publicPort' => 'nullable|integer',
'isLogDrainEnabled' => 'nullable|boolean',
'customDockerRunOptions' => 'nullable',
'enableSsl' => 'boolean',
'sslMode' => 'nullable|string|in:allow,prefer,require,verify-full',
];
}
@ -65,45 +90,90 @@ protected function messages(): array
return array_merge(
ValidationPatterns::combinedMessages(),
[
'database.name.required' => 'The Name field is required.',
'database.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
'database.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
'database.mongo_initdb_root_username.required' => 'The Root Username field is required.',
'database.mongo_initdb_root_password.required' => 'The Root Password field is required.',
'database.mongo_initdb_database.required' => 'The MongoDB Database field is required.',
'database.image.required' => 'The Docker Image field is required.',
'database.public_port.integer' => 'The Public Port must be an integer.',
'database.ssl_mode.in' => 'The SSL Mode must be one of: allow, prefer, require, verify-full.',
'name.required' => 'The Name field is required.',
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
'mongoInitdbRootUsername.required' => 'The Root Username field is required.',
'mongoInitdbRootPassword.required' => 'The Root Password field is required.',
'mongoInitdbDatabase.required' => 'The MongoDB Database field is required.',
'image.required' => 'The Docker Image field is required.',
'publicPort.integer' => 'The Public Port must be an integer.',
'sslMode.in' => 'The SSL Mode must be one of: allow, prefer, require, verify-full.',
]
);
}
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.mongo_conf' => 'Mongo Configuration',
'database.mongo_initdb_root_username' => 'Root Username',
'database.mongo_initdb_root_password' => 'Root Password',
'database.mongo_initdb_database' => 'Database',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
'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',
'name' => 'Name',
'description' => 'Description',
'mongoConf' => 'Mongo Configuration',
'mongoInitdbRootUsername' => 'Root Username',
'mongoInitdbRootPassword' => 'Root Password',
'mongoInitdbDatabase' => 'Database',
'image' => 'Image',
'portsMappings' => 'Port Mapping',
'isPublic' => 'Is Public',
'publicPort' => 'Public Port',
'customDockerRunOptions' => 'Custom Docker Run Options',
'enableSsl' => 'Enable SSL',
'sslMode' => 'SSL Mode',
];
public function mount()
{
$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');
try {
$this->syncData();
$this->server = data_get($this->database, 'destination.server');
$existingCert = $this->database->sslCertificates()->first();
$existingCert = $this->database->sslCertificates()->first();
if ($existingCert) {
$this->certificateValidUntil = $existingCert->valid_until;
if ($existingCert) {
$this->certificateValidUntil = $existingCert->valid_until;
}
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->database->name = $this->name;
$this->database->description = $this->description;
$this->database->mongo_conf = $this->mongoConf;
$this->database->mongo_initdb_root_username = $this->mongoInitdbRootUsername;
$this->database->mongo_initdb_root_password = $this->mongoInitdbRootPassword;
$this->database->mongo_initdb_database = $this->mongoInitdbDatabase;
$this->database->image = $this->image;
$this->database->ports_mappings = $this->portsMappings;
$this->database->is_public = $this->isPublic;
$this->database->public_port = $this->publicPort;
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
$this->database->enable_ssl = $this->enableSsl;
$this->database->ssl_mode = $this->sslMode;
$this->database->save();
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
} else {
$this->name = $this->database->name;
$this->description = $this->database->description;
$this->mongoConf = $this->database->mongo_conf;
$this->mongoInitdbRootUsername = $this->database->mongo_initdb_root_username;
$this->mongoInitdbRootPassword = $this->database->mongo_initdb_root_password;
$this->mongoInitdbDatabase = $this->database->mongo_initdb_database;
$this->image = $this->database->image;
$this->portsMappings = $this->database->ports_mappings;
$this->isPublic = $this->database->is_public;
$this->publicPort = $this->database->public_port;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->enableSsl = $this->database->enable_ssl;
$this->sslMode = $this->database->ssl_mode;
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
}
}
@ -113,12 +183,12 @@ public function instantSaveAdvanced()
$this->authorize('update', $this->database);
if (! $this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->isLogDrainEnabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;
}
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Database updated.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
@ -131,14 +201,13 @@ public function submit()
try {
$this->authorize('update', $this->database);
if (str($this->database->public_port)->isEmpty()) {
$this->database->public_port = null;
if (str($this->publicPort)->isEmpty()) {
$this->publicPort = null;
}
if (str($this->database->mongo_conf)->isEmpty()) {
$this->database->mongo_conf = null;
if (str($this->mongoConf)->isEmpty()) {
$this->mongoConf = null;
}
$this->validate();
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Database updated.');
} catch (Exception $e) {
return handleError($e, $this);
@ -156,16 +225,16 @@ public function instantSave()
try {
$this->authorize('update', $this->database);
if ($this->database->is_public && ! $this->database->public_port) {
if ($this->isPublic && ! $this->publicPort) {
$this->dispatch('error', 'Public port is required.');
$this->database->is_public = false;
$this->isPublic = false;
return;
}
if ($this->database->is_public) {
if ($this->isPublic) {
if (! str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false;
$this->isPublic = false;
return;
}
@ -175,16 +244,15 @@ public function instantSave()
StopDatabaseProxy::run($this->database);
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
$this->db_url_public = $this->database->external_db_url;
$this->database->save();
$this->syncData(true);
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
$this->isPublic = ! $this->isPublic;
return handleError($e, $this);
}
}
public function updatedDatabaseSslMode()
public function updatedSslMode()
{
$this->instantSaveSSL();
}
@ -194,7 +262,7 @@ public function instantSaveSSL()
try {
$this->authorize('update', $this->database);
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
@ -238,6 +306,7 @@ public function regenerateSslCertificate()
public function refresh(): void
{
$this->database->refresh();
$this->syncData();
}
public function render()

View file

@ -18,12 +18,40 @@ class General extends Component
{
use AuthorizesRequests;
protected $listeners = ['refresh'];
public StandaloneMysql $database;
public Server $server;
public string $name;
public ?string $description = null;
public string $mysqlRootPassword;
public string $mysqlUser;
public string $mysqlPassword;
public string $mysqlDatabase;
public ?string $mysqlConf = null;
public string $image;
public ?string $portsMappings = null;
public ?bool $isPublic = null;
public ?int $publicPort = null;
public bool $isLogDrainEnabled = false;
public ?string $customDockerRunOptions = null;
public bool $enableSsl = false;
public ?string $sslMode = null;
public ?string $db_url = null;
public ?string $db_url_public = null;
@ -36,28 +64,27 @@ public function getListeners()
return [
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
'refresh' => '$refresh',
];
}
protected function rules(): array
{
return [
'database.name' => ValidationPatterns::nameRules(),
'database.description' => ValidationPatterns::descriptionRules(),
'database.mysql_root_password' => 'required',
'database.mysql_user' => 'required',
'database.mysql_password' => 'required',
'database.mysql_database' => 'required',
'database.mysql_conf' => 'nullable',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean',
'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',
'name' => ValidationPatterns::nameRules(),
'description' => ValidationPatterns::descriptionRules(),
'mysqlRootPassword' => 'required',
'mysqlUser' => 'required',
'mysqlPassword' => 'required',
'mysqlDatabase' => 'required',
'mysqlConf' => 'nullable',
'image' => 'required',
'portsMappings' => 'nullable',
'isPublic' => 'nullable|boolean',
'publicPort' => 'nullable|integer',
'isLogDrainEnabled' => 'nullable|boolean',
'customDockerRunOptions' => 'nullable',
'enableSsl' => 'boolean',
'sslMode' => 'nullable|string|in:PREFERRED,REQUIRED,VERIFY_CA,VERIFY_IDENTITY',
];
}
@ -66,47 +93,94 @@ protected function messages(): array
return array_merge(
ValidationPatterns::combinedMessages(),
[
'database.name.required' => 'The Name field is required.',
'database.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
'database.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
'database.mysql_root_password.required' => 'The Root Password field is required.',
'database.mysql_user.required' => 'The MySQL User field is required.',
'database.mysql_password.required' => 'The MySQL Password field is required.',
'database.mysql_database.required' => 'The MySQL Database field is required.',
'database.image.required' => 'The Docker Image field is required.',
'database.public_port.integer' => 'The Public Port must be an integer.',
'database.ssl_mode.in' => 'The SSL Mode must be one of: PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY.',
'name.required' => 'The Name field is required.',
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
'mysqlRootPassword.required' => 'The Root Password field is required.',
'mysqlUser.required' => 'The MySQL User field is required.',
'mysqlPassword.required' => 'The MySQL Password field is required.',
'mysqlDatabase.required' => 'The MySQL Database field is required.',
'image.required' => 'The Docker Image field is required.',
'publicPort.integer' => 'The Public Port must be an integer.',
'sslMode.in' => 'The SSL Mode must be one of: PREFERRED, REQUIRED, VERIFY_CA, VERIFY_IDENTITY.',
]
);
}
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.mysql_root_password' => 'Root Password',
'database.mysql_user' => 'User',
'database.mysql_password' => 'Password',
'database.mysql_database' => 'Database',
'database.mysql_conf' => 'MySQL Configuration',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
'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',
'name' => 'Name',
'description' => 'Description',
'mysqlRootPassword' => 'Root Password',
'mysqlUser' => 'User',
'mysqlPassword' => 'Password',
'mysqlDatabase' => 'Database',
'mysqlConf' => 'MySQL Configuration',
'image' => 'Image',
'portsMappings' => 'Port Mapping',
'isPublic' => 'Is Public',
'publicPort' => 'Public Port',
'customDockerRunOptions' => 'Custom Docker Run Options',
'enableSsl' => 'Enable SSL',
'sslMode' => 'SSL Mode',
];
public function mount()
{
$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');
try {
$this->syncData();
$this->server = data_get($this->database, 'destination.server');
$existingCert = $this->database->sslCertificates()->first();
$existingCert = $this->database->sslCertificates()->first();
if ($existingCert) {
$this->certificateValidUntil = $existingCert->valid_until;
if ($existingCert) {
$this->certificateValidUntil = $existingCert->valid_until;
}
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->database->name = $this->name;
$this->database->description = $this->description;
$this->database->mysql_root_password = $this->mysqlRootPassword;
$this->database->mysql_user = $this->mysqlUser;
$this->database->mysql_password = $this->mysqlPassword;
$this->database->mysql_database = $this->mysqlDatabase;
$this->database->mysql_conf = $this->mysqlConf;
$this->database->image = $this->image;
$this->database->ports_mappings = $this->portsMappings;
$this->database->is_public = $this->isPublic;
$this->database->public_port = $this->publicPort;
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
$this->database->enable_ssl = $this->enableSsl;
$this->database->ssl_mode = $this->sslMode;
$this->database->save();
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
} else {
$this->name = $this->database->name;
$this->description = $this->database->description;
$this->mysqlRootPassword = $this->database->mysql_root_password;
$this->mysqlUser = $this->database->mysql_user;
$this->mysqlPassword = $this->database->mysql_password;
$this->mysqlDatabase = $this->database->mysql_database;
$this->mysqlConf = $this->database->mysql_conf;
$this->image = $this->database->image;
$this->portsMappings = $this->database->ports_mappings;
$this->isPublic = $this->database->is_public;
$this->publicPort = $this->database->public_port;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->enableSsl = $this->database->enable_ssl;
$this->sslMode = $this->database->ssl_mode;
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
}
}
@ -116,12 +190,12 @@ public function instantSaveAdvanced()
$this->authorize('update', $this->database);
if (! $this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->isLogDrainEnabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;
}
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Database updated.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
@ -134,11 +208,10 @@ public function submit()
try {
$this->authorize('update', $this->database);
if (str($this->database->public_port)->isEmpty()) {
$this->database->public_port = null;
if (str($this->publicPort)->isEmpty()) {
$this->publicPort = null;
}
$this->validate();
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Database updated.');
} catch (Exception $e) {
return handleError($e, $this);
@ -156,16 +229,16 @@ public function instantSave()
try {
$this->authorize('update', $this->database);
if ($this->database->is_public && ! $this->database->public_port) {
if ($this->isPublic && ! $this->publicPort) {
$this->dispatch('error', 'Public port is required.');
$this->database->is_public = false;
$this->isPublic = false;
return;
}
if ($this->database->is_public) {
if ($this->isPublic) {
if (! str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false;
$this->isPublic = false;
return;
}
@ -175,16 +248,15 @@ public function instantSave()
StopDatabaseProxy::run($this->database);
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
$this->db_url_public = $this->database->external_db_url;
$this->database->save();
$this->syncData(true);
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
$this->isPublic = ! $this->isPublic;
return handleError($e, $this);
}
}
public function updatedDatabaseSslMode()
public function updatedSslMode()
{
$this->instantSaveSSL();
}
@ -194,7 +266,7 @@ public function instantSaveSSL()
try {
$this->authorize('update', $this->database);
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
@ -238,6 +310,7 @@ public function regenerateSslCertificate()
public function refresh(): void
{
$this->database->refresh();
$this->syncData();
}
public function render()

View file

@ -22,6 +22,40 @@ class General extends Component
public Server $server;
public string $name;
public ?string $description = null;
public string $postgresUser;
public string $postgresPassword;
public string $postgresDb;
public ?string $postgresInitdbArgs = null;
public ?string $postgresHostAuthMethod = null;
public ?string $postgresConf = null;
public ?array $initScripts = null;
public string $image;
public ?string $portsMappings = null;
public ?bool $isPublic = null;
public ?int $publicPort = null;
public bool $isLogDrainEnabled = false;
public ?string $customDockerRunOptions = null;
public bool $enableSsl = false;
public ?string $sslMode = null;
public string $new_filename;
public string $new_content;
@ -38,7 +72,6 @@ public function getListeners()
return [
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
'refresh' => '$refresh',
'save_init_script',
'delete_init_script',
];
@ -47,23 +80,23 @@ public function getListeners()
protected function rules(): array
{
return [
'database.name' => ValidationPatterns::nameRules(),
'database.description' => ValidationPatterns::descriptionRules(),
'database.postgres_user' => 'required',
'database.postgres_password' => 'required',
'database.postgres_db' => 'required',
'database.postgres_initdb_args' => 'nullable',
'database.postgres_host_auth_method' => 'nullable',
'database.postgres_conf' => 'nullable',
'database.init_scripts' => 'nullable',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean',
'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',
'name' => ValidationPatterns::nameRules(),
'description' => ValidationPatterns::descriptionRules(),
'postgresUser' => 'required',
'postgresPassword' => 'required',
'postgresDb' => 'required',
'postgresInitdbArgs' => 'nullable',
'postgresHostAuthMethod' => 'nullable',
'postgresConf' => 'nullable',
'initScripts' => 'nullable',
'image' => 'required',
'portsMappings' => 'nullable',
'isPublic' => 'nullable|boolean',
'publicPort' => 'nullable|integer',
'isLogDrainEnabled' => 'nullable|boolean',
'customDockerRunOptions' => 'nullable',
'enableSsl' => 'boolean',
'sslMode' => 'nullable|string|in:allow,prefer,require,verify-ca,verify-full',
];
}
@ -72,48 +105,99 @@ protected function messages(): array
return array_merge(
ValidationPatterns::combinedMessages(),
[
'database.name.required' => 'The Name field is required.',
'database.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
'database.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
'database.postgres_user.required' => 'The Postgres User field is required.',
'database.postgres_password.required' => 'The Postgres Password field is required.',
'database.postgres_db.required' => 'The Postgres Database field is required.',
'database.image.required' => 'The Docker Image field is required.',
'database.public_port.integer' => 'The Public Port must be an integer.',
'database.ssl_mode.in' => 'The SSL Mode must be one of: allow, prefer, require, verify-ca, verify-full.',
'name.required' => 'The Name field is required.',
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
'postgresUser.required' => 'The Postgres User field is required.',
'postgresPassword.required' => 'The Postgres Password field is required.',
'postgresDb.required' => 'The Postgres Database field is required.',
'image.required' => 'The Docker Image field is required.',
'publicPort.integer' => 'The Public Port must be an integer.',
'sslMode.in' => 'The SSL Mode must be one of: allow, prefer, require, verify-ca, verify-full.',
]
);
}
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.postgres_user' => 'Postgres User',
'database.postgres_password' => 'Postgres Password',
'database.postgres_db' => 'Postgres DB',
'database.postgres_initdb_args' => 'Postgres Initdb Args',
'database.postgres_host_auth_method' => 'Postgres Host Auth Method',
'database.postgres_conf' => 'Postgres Configuration',
'database.init_scripts' => 'Init Scripts',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
'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',
'name' => 'Name',
'description' => 'Description',
'postgresUser' => 'Postgres User',
'postgresPassword' => 'Postgres Password',
'postgresDb' => 'Postgres DB',
'postgresInitdbArgs' => 'Postgres Initdb Args',
'postgresHostAuthMethod' => 'Postgres Host Auth Method',
'postgresConf' => 'Postgres Configuration',
'initScripts' => 'Init Scripts',
'image' => 'Image',
'portsMappings' => 'Port Mapping',
'isPublic' => 'Is Public',
'publicPort' => 'Public Port',
'customDockerRunOptions' => 'Custom Docker Run Options',
'enableSsl' => 'Enable SSL',
'sslMode' => 'SSL Mode',
];
public function mount()
{
$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');
try {
$this->syncData();
$this->server = data_get($this->database, 'destination.server');
$existingCert = $this->database->sslCertificates()->first();
$existingCert = $this->database->sslCertificates()->first();
if ($existingCert) {
$this->certificateValidUntil = $existingCert->valid_until;
if ($existingCert) {
$this->certificateValidUntil = $existingCert->valid_until;
}
} catch (Exception $e) {
return handleError($e, $this);
}
}
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->database->name = $this->name;
$this->database->description = $this->description;
$this->database->postgres_user = $this->postgresUser;
$this->database->postgres_password = $this->postgresPassword;
$this->database->postgres_db = $this->postgresDb;
$this->database->postgres_initdb_args = $this->postgresInitdbArgs;
$this->database->postgres_host_auth_method = $this->postgresHostAuthMethod;
$this->database->postgres_conf = $this->postgresConf;
$this->database->init_scripts = $this->initScripts;
$this->database->image = $this->image;
$this->database->ports_mappings = $this->portsMappings;
$this->database->is_public = $this->isPublic;
$this->database->public_port = $this->publicPort;
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
$this->database->enable_ssl = $this->enableSsl;
$this->database->ssl_mode = $this->sslMode;
$this->database->save();
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
} else {
$this->name = $this->database->name;
$this->description = $this->database->description;
$this->postgresUser = $this->database->postgres_user;
$this->postgresPassword = $this->database->postgres_password;
$this->postgresDb = $this->database->postgres_db;
$this->postgresInitdbArgs = $this->database->postgres_initdb_args;
$this->postgresHostAuthMethod = $this->database->postgres_host_auth_method;
$this->postgresConf = $this->database->postgres_conf;
$this->initScripts = $this->database->init_scripts;
$this->image = $this->database->image;
$this->portsMappings = $this->database->ports_mappings;
$this->isPublic = $this->database->is_public;
$this->publicPort = $this->database->public_port;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->enableSsl = $this->database->enable_ssl;
$this->sslMode = $this->database->ssl_mode;
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
}
}
@ -123,12 +207,12 @@ public function instantSaveAdvanced()
$this->authorize('update', $this->database);
if (! $this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->isLogDrainEnabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;
}
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Database updated.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
@ -136,7 +220,7 @@ public function instantSaveAdvanced()
}
}
public function updatedDatabaseSslMode()
public function updatedSslMode()
{
$this->instantSaveSSL();
}
@ -146,10 +230,8 @@ public function instantSaveSSL()
try {
$this->authorize('update', $this->database);
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'SSL configuration updated.');
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
} catch (Exception $e) {
return handleError($e, $this);
}
@ -194,16 +276,16 @@ public function instantSave()
try {
$this->authorize('update', $this->database);
if ($this->database->is_public && ! $this->database->public_port) {
if ($this->isPublic && ! $this->publicPort) {
$this->dispatch('error', 'Public port is required.');
$this->database->is_public = false;
$this->isPublic = false;
return;
}
if ($this->database->is_public) {
if ($this->isPublic) {
if (! str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false;
$this->isPublic = false;
return;
}
@ -213,10 +295,9 @@ public function instantSave()
StopDatabaseProxy::run($this->database);
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
$this->db_url_public = $this->database->external_db_url;
$this->database->save();
$this->syncData(true);
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
$this->isPublic = ! $this->isPublic;
return handleError($e, $this);
}
@ -226,7 +307,7 @@ public function save_init_script($script)
{
$this->authorize('update', $this->database);
$initScripts = collect($this->database->init_scripts ?? []);
$initScripts = collect($this->initScripts ?? []);
$existingScript = $initScripts->firstWhere('filename', $script['filename']);
$oldScript = $initScripts->firstWhere('index', $script['index']);
@ -262,7 +343,7 @@ public function save_init_script($script)
$initScripts->push($script);
}
$this->database->init_scripts = $initScripts->values()
$this->initScripts = $initScripts->values()
->map(function ($item, $index) {
$item['index'] = $index;
@ -270,7 +351,7 @@ public function save_init_script($script)
})
->all();
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Init script saved and updated.');
}
@ -278,7 +359,7 @@ public function delete_init_script($script)
{
$this->authorize('update', $this->database);
$collection = collect($this->database->init_scripts);
$collection = collect($this->initScripts);
$found = $collection->firstWhere('filename', $script['filename']);
if ($found) {
$container_name = $this->database->uuid;
@ -303,8 +384,8 @@ public function delete_init_script($script)
})
->all();
$this->database->init_scripts = $updatedScripts;
$this->database->save();
$this->initScripts = $updatedScripts;
$this->syncData(true);
$this->dispatch('refresh')->self();
$this->dispatch('success', 'Init script deleted from the database and the server.');
}
@ -318,23 +399,23 @@ public function save_new_init_script()
'new_filename' => 'required|string',
'new_content' => 'required|string',
]);
$found = collect($this->database->init_scripts)->firstWhere('filename', $this->new_filename);
$found = collect($this->initScripts)->firstWhere('filename', $this->new_filename);
if ($found) {
$this->dispatch('error', 'Filename already exists.');
return;
}
if (! isset($this->database->init_scripts)) {
$this->database->init_scripts = [];
if (! isset($this->initScripts)) {
$this->initScripts = [];
}
$this->database->init_scripts = array_merge($this->database->init_scripts, [
$this->initScripts = array_merge($this->initScripts, [
[
'index' => count($this->database->init_scripts),
'index' => count($this->initScripts),
'filename' => $this->new_filename,
'content' => $this->new_content,
],
]);
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Init script added.');
$this->new_content = '';
$this->new_filename = '';
@ -345,11 +426,10 @@ public function submit()
try {
$this->authorize('update', $this->database);
if (str($this->database->public_port)->isEmpty()) {
$this->database->public_port = null;
if (str($this->publicPort)->isEmpty()) {
$this->publicPort = null;
}
$this->validate();
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Database updated.');
} catch (Exception $e) {
return handleError($e, $this);

View file

@ -22,15 +22,35 @@ class General extends Component
public StandaloneRedis $database;
public string $redis_username;
public string $name;
public ?string $redis_password;
public ?string $description = null;
public string $redis_version;
public ?string $redisConf = null;
public ?string $db_url = null;
public string $image;
public ?string $db_url_public = null;
public ?string $portsMappings = null;
public ?bool $isPublic = null;
public ?int $publicPort = null;
public bool $isLogDrainEnabled = false;
public ?string $customDockerRunOptions = null;
public string $redisUsername;
public string $redisPassword;
public string $redisVersion;
public ?string $dbUrl = null;
public ?string $dbUrlPublic = null;
public bool $enableSsl = false;
public ?Carbon $certificateValidUntil = null;
@ -41,25 +61,24 @@ public function getListeners()
return [
"echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
'envsUpdated' => 'refresh',
'refresh',
];
}
protected function rules(): array
{
return [
'database.name' => ValidationPatterns::nameRules(),
'database.description' => ValidationPatterns::descriptionRules(),
'database.redis_conf' => 'nullable',
'database.image' => 'required',
'database.ports_mappings' => 'nullable',
'database.is_public' => 'nullable|boolean',
'database.public_port' => 'nullable|integer',
'database.is_log_drain_enabled' => 'nullable|boolean',
'database.custom_docker_run_options' => 'nullable',
'redis_username' => 'required',
'redis_password' => 'required',
'database.enable_ssl' => 'boolean',
'name' => ValidationPatterns::nameRules(),
'description' => ValidationPatterns::descriptionRules(),
'redisConf' => 'nullable',
'image' => 'required',
'portsMappings' => 'nullable',
'isPublic' => 'nullable|boolean',
'publicPort' => 'nullable|integer',
'isLogDrainEnabled' => 'nullable|boolean',
'customDockerRunOptions' => 'nullable',
'redisUsername' => 'required',
'redisPassword' => 'required',
'enableSsl' => 'boolean',
];
}
@ -68,39 +87,81 @@ protected function messages(): array
return array_merge(
ValidationPatterns::combinedMessages(),
[
'database.name.required' => 'The Name field is required.',
'database.name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
'database.description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
'database.image.required' => 'The Docker Image field is required.',
'database.public_port.integer' => 'The Public Port must be an integer.',
'redis_username.required' => 'The Redis Username field is required.',
'redis_password.required' => 'The Redis Password field is required.',
'name.required' => 'The Name field is required.',
'name.regex' => 'The Name may only contain letters, numbers, spaces, dashes (-), underscores (_), dots (.), slashes (/), colons (:), and parentheses ().',
'description.regex' => 'The Description contains invalid characters. Only letters, numbers, spaces, and common punctuation (- _ . : / () \' " , ! ? @ # % & + = [] {} | ~ ` *) are allowed.',
'image.required' => 'The Docker Image field is required.',
'publicPort.integer' => 'The Public Port must be an integer.',
'redisUsername.required' => 'The Redis Username field is required.',
'redisPassword.required' => 'The Redis Password field is required.',
]
);
}
protected $validationAttributes = [
'database.name' => 'Name',
'database.description' => 'Description',
'database.redis_conf' => 'Redis Configuration',
'database.image' => 'Image',
'database.ports_mappings' => 'Port Mapping',
'database.is_public' => 'Is Public',
'database.public_port' => 'Public Port',
'database.custom_docker_run_options' => 'Custom Docker Options',
'redis_username' => 'Redis Username',
'redis_password' => 'Redis Password',
'database.enable_ssl' => 'Enable SSL',
'name' => 'Name',
'description' => 'Description',
'redisConf' => 'Redis Configuration',
'image' => 'Image',
'portsMappings' => 'Port Mapping',
'isPublic' => 'Is Public',
'publicPort' => 'Public Port',
'customDockerRunOptions' => 'Custom Docker Options',
'redisUsername' => 'Redis Username',
'redisPassword' => 'Redis Password',
'enableSsl' => 'Enable SSL',
];
public function mount()
{
$this->server = data_get($this->database, 'destination.server');
$this->refreshView();
$existingCert = $this->database->sslCertificates()->first();
try {
$this->syncData();
$this->server = data_get($this->database, 'destination.server');
if ($existingCert) {
$this->certificateValidUntil = $existingCert->valid_until;
$existingCert = $this->database->sslCertificates()->first();
if ($existingCert) {
$this->certificateValidUntil = $existingCert->valid_until;
}
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function syncData(bool $toModel = false)
{
if ($toModel) {
$this->validate();
$this->database->name = $this->name;
$this->database->description = $this->description;
$this->database->redis_conf = $this->redisConf;
$this->database->image = $this->image;
$this->database->ports_mappings = $this->portsMappings;
$this->database->is_public = $this->isPublic;
$this->database->public_port = $this->publicPort;
$this->database->is_log_drain_enabled = $this->isLogDrainEnabled;
$this->database->custom_docker_run_options = $this->customDockerRunOptions;
$this->database->enable_ssl = $this->enableSsl;
$this->database->save();
$this->dbUrl = $this->database->internal_db_url;
$this->dbUrlPublic = $this->database->external_db_url;
} else {
$this->name = $this->database->name;
$this->description = $this->database->description;
$this->redisConf = $this->database->redis_conf;
$this->image = $this->database->image;
$this->portsMappings = $this->database->ports_mappings;
$this->isPublic = $this->database->is_public;
$this->publicPort = $this->database->public_port;
$this->isLogDrainEnabled = $this->database->is_log_drain_enabled;
$this->customDockerRunOptions = $this->database->custom_docker_run_options;
$this->enableSsl = $this->database->enable_ssl;
$this->dbUrl = $this->database->internal_db_url;
$this->dbUrlPublic = $this->database->external_db_url;
$this->redisVersion = $this->database->getRedisVersion();
$this->redisUsername = $this->database->redis_username;
$this->redisPassword = $this->database->redis_password;
}
}
@ -110,12 +171,12 @@ public function instantSaveAdvanced()
$this->authorize('update', $this->database);
if (! $this->server->isLogDrainEnabled()) {
$this->database->is_log_drain_enabled = false;
$this->isLogDrainEnabled = false;
$this->dispatch('error', 'Log drain is not enabled on the server. Please enable it first.');
return;
}
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'Database updated.');
$this->dispatch('success', 'You need to restart the service for the changes to take effect.');
} catch (Exception $e) {
@ -128,20 +189,19 @@ public function submit()
try {
$this->authorize('manageEnvironment', $this->database);
$this->validate();
$this->syncData(true);
if (version_compare($this->redis_version, '6.0', '>=')) {
if (version_compare($this->redisVersion, '6.0', '>=')) {
$this->database->runtime_environment_variables()->updateOrCreate(
['key' => 'REDIS_USERNAME'],
['value' => $this->redis_username, 'resourceable_id' => $this->database->id]
['value' => $this->redisUsername, 'resourceable_id' => $this->database->id]
);
}
$this->database->runtime_environment_variables()->updateOrCreate(
['key' => 'REDIS_PASSWORD'],
['value' => $this->redis_password, 'resourceable_id' => $this->database->id]
['value' => $this->redisPassword, 'resourceable_id' => $this->database->id]
);
$this->database->save();
$this->dispatch('success', 'Database updated.');
} catch (Exception $e) {
return handleError($e, $this);
@ -155,16 +215,16 @@ public function instantSave()
try {
$this->authorize('update', $this->database);
if ($this->database->is_public && ! $this->database->public_port) {
if ($this->isPublic && ! $this->publicPort) {
$this->dispatch('error', 'Public port is required.');
$this->database->is_public = false;
$this->isPublic = false;
return;
}
if ($this->database->is_public) {
if ($this->isPublic) {
if (! str($this->database->status)->startsWith('running')) {
$this->dispatch('error', 'Database must be started to be publicly accessible.');
$this->database->is_public = false;
$this->isPublic = false;
return;
}
@ -174,10 +234,11 @@ public function instantSave()
StopDatabaseProxy::run($this->database);
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
$this->db_url_public = $this->database->external_db_url;
$this->database->save();
$this->dbUrlPublic = $this->database->external_db_url;
$this->syncData(true);
} catch (\Throwable $e) {
$this->database->is_public = ! $this->database->is_public;
$this->isPublic = ! $this->isPublic;
$this->syncData(true);
return handleError($e, $this);
}
@ -188,7 +249,7 @@ public function instantSaveSSL()
try {
$this->authorize('update', $this->database);
$this->database->save();
$this->syncData(true);
$this->dispatch('success', 'SSL configuration updated.');
} catch (Exception $e) {
return handleError($e, $this);
@ -232,16 +293,7 @@ public function regenerateSslCertificate()
public function refresh(): void
{
$this->database->refresh();
$this->refreshView();
}
private function refreshView()
{
$this->db_url = $this->database->internal_db_url;
$this->db_url_public = $this->database->external_db_url;
$this->redis_version = $this->database->getRedisVersion();
$this->redis_username = $this->database->redis_username;
$this->redis_password = $this->database->redis_password;
$this->syncData();
}
public function render()

View file

@ -7,9 +7,9 @@
</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="database.name" canGate="update" :canResource="$database" />
<x-forms.input label="Description" id="database.description" canGate="update" :canResource="$database" />
<x-forms.input label="Image" id="database.image" required canGate="update" :canResource="$database"
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
<x-forms.input label="Description" id="description" canGate="update" :canResource="$database" />
<x-forms.input label="Image" id="image" required canGate="update" :canResource="$database"
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/mariadb'>https://hub.docker.com/_/mariadb</a>" />
</div>
<div class="pt-2 dark:text-warning">If you change the values in the database, please sync it here, otherwise
@ -17,32 +17,32 @@
</div>
@if ($database->started_at)
<div class="flex xl:flex-row flex-col gap-2">
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password" required
<x-forms.input label="Root Password" id="mariadbRootPassword" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work."
canGate="update" :canResource="$database" />
<x-forms.input label="Normal User" id="database.mariadb_user" required
<x-forms.input label="Normal User" id="mariadbUser" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work."
canGate="update" :canResource="$database" />
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
<x-forms.input label="Normal User Password" id="mariadbPassword" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work."
canGate="update" :canResource="$database" />
</div>
<div class="flex flex-col gap-2">
<x-forms.input label="Initial Database" id="database.mariadb_database"
<x-forms.input label="Initial Database" id="mariadbDatabase"
placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." />
</div>
@else
<div class="flex xl:flex-row flex-col gap-2 pb-2">
<x-forms.input label="Root Password" id="database.mariadb_root_password" type="password"
<x-forms.input label="Root Password" id="mariadbRootPassword" type="password"
helper="You can only change this in the database." canGate="update" :canResource="$database" />
<x-forms.input label="Normal User" id="database.mariadb_user" required
<x-forms.input label="Normal User" id="mariadbUser" required
helper="You can only change this in the database." canGate="update" :canResource="$database" />
<x-forms.input label="Normal User Password" id="database.mariadb_password" type="password" required
<x-forms.input label="Normal User Password" id="mariadbPassword" type="password" required
helper="You can only change this in the database." canGate="update" :canResource="$database" />
</div>
<div class="flex flex-col gap-2">
<x-forms.input label="Initial Database" id="database.mariadb_database"
<x-forms.input label="Initial Database" id="mariadbDatabase"
placeholder="If empty, it will be the same as Username."
helper="You can only change this in the database." canGate="update" :canResource="$database" />
</div>
@ -51,13 +51,13 @@
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" canGate="update"
id="customDockerRunOptions" label="Custom Docker Options" canGate="update"
:canResource="$database" />
</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"
<x-forms.input placeholder="3000:5432" id="portsMappings" 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 dark:text-warning'>Example</span>3000:5432,3002:5433"
canGate="update" :canResource="$database" />
</div>
@ -75,7 +75,7 @@
<div class="flex items-center justify-between py-2">
<div class="flex items-center justify-between w-full">
<h3>SSL Configuration</h3>
@if ($database->enable_ssl && $certificateValidUntil)
@if ($enableSsl && $certificateValidUntil)
<x-modal-confirmation title="Regenerate SSL Certificates"
buttonTitle="Regenerate SSL Certificates" :actions="[
'The SSL certificate of this database will be regenerated.',
@ -85,7 +85,7 @@
@endif
</div>
</div>
@if ($database->enable_ssl && $certificateValidUntil)
@if ($enableSsl && $certificateValidUntil)
<span class="text-sm">Valid until:
@if (now()->gt($certificateValidUntil))
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expired</span>
@ -102,12 +102,12 @@
<div class="flex flex-col gap-2">
<div class="w-64">
@if (str($database->status)->contains('exited'))
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" canGate="update"
<x-forms.checkbox id="enableSsl" label="Enable SSL"
wire:model.live="enableSsl" instantSave="instantSaveSSL" canGate="update"
:canResource="$database" />
@else
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" disabled
<x-forms.checkbox id="enableSsl" label="Enable SSL"
wire:model.live="enableSsl" instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this settings." canGate="update"
:canResource="$database" />
@endif
@ -134,18 +134,18 @@
</x-slide-over>
@endif
</div>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available"
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available"
canGate="update" :canResource="$database" />
</div>
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" canGate="update" :canResource="$database" />
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}"
id="publicPort" label="Public Port" canGate="update" :canResource="$database" />
</div>
<x-forms.textarea label="Custom MariaDB Configuration" rows="10" id="database.mariadb_conf"
<x-forms.textarea label="Custom MariaDB Configuration" rows="10" id="mariadbConf"
canGate="update" :canResource="$database" />
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs"
instantSave="instantSaveAdvanced" id="isLogDrainEnabled" label="Drain Logs"
canGate="update" :canResource="$database" />
</div>
</form>

View file

@ -7,9 +7,9 @@
</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="database.name" canGate="update" :canResource="$database" />
<x-forms.input label="Description" id="database.description" canGate="update" :canResource="$database" />
<x-forms.input label="Image" id="database.image" required canGate="update" :canResource="$database"
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
<x-forms.input label="Description" id="description" canGate="update" :canResource="$database" />
<x-forms.input label="Image" id="image" required canGate="update" :canResource="$database"
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/mongo'>https://hub.docker.com/_/mongo</a>" />
</div>
<div class="pt-2 dark:text-warning">If you change the values in the database, please sync it here, otherwise
@ -17,36 +17,36 @@
</div>
@if ($database->started_at)
<div class="flex xl:flex-row flex-col gap-2">
<x-forms.input label="Initial Username" id="database.mongo_initdb_root_username"
<x-forms.input label="Initial Username" id="mongoInitdbRootUsername"
placeholder="If empty: postgres"
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work."
canGate="update" :canResource="$database" />
<x-forms.input label="Initial Password" id="database.mongo_initdb_root_password" type="password"
<x-forms.input label="Initial Password" id="mongoInitdbRootPassword" type="password"
required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work."
canGate="update" :canResource="$database" />
<x-forms.input label="Initial Database" id="database.mongo_initdb_database"
<x-forms.input label="Initial Database" id="mongoInitdbDatabase"
placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." canGate="update" :canResource="$database" />
</div>
@else
<div class="flex xl:flex-row flex-col gap-2 pb-2">
<x-forms.input required label="Username" id="database.mongo_initdb_root_username"
<x-forms.input required label="Username" id="mongoInitdbRootUsername"
placeholder="If empty: postgres" canGate="update" :canResource="$database" />
<x-forms.input label="Password" id="database.mongo_initdb_root_password" type="password" required
<x-forms.input label="Password" id="mongoInitdbRootPassword" type="password" required
canGate="update" :canResource="$database" />
<x-forms.input required label="Database" id="database.mongo_initdb_database"
<x-forms.input required label="Database" id="mongoInitdbDatabase"
placeholder="If empty, it will be the same as Username." canGate="update" :canResource="$database" />
</div>
@endif
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" canGate="update" :canResource="$database" />
id="customDockerRunOptions" label="Custom Docker Options" canGate="update" :canResource="$database" />
<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"
<x-forms.input placeholder="3000:5432" id="portsMappings" 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 dark:text-warning'>Example</span>3000:5432,3002:5433"
canGate="update" :canResource="$database" />
</div>
@ -64,7 +64,7 @@
<div class="flex items-center justify-between py-2">
<div class="flex items-center justify-between w-full">
<h3>SSL Configuration</h3>
@if ($database->enable_ssl)
@if ($enableSsl)
<x-modal-confirmation title="Regenerate SSL Certificates"
buttonTitle="Regenerate SSL Certificates" :actions="[
'The SSL certificate of this database will be regenerated.',
@ -74,7 +74,7 @@
@endif
</div>
</div>
@if ($database->enable_ssl && $certificateValidUntil)
@if ($enableSsl && $certificateValidUntil)
<span class="text-sm">Valid until:
@if (now()->gt($certificateValidUntil))
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expired</span>
@ -91,20 +91,20 @@
<div class="flex flex-col gap-2">
<div class="w-64">
@if (str($database->status)->contains('exited'))
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" canGate="update"
<x-forms.checkbox id="enableSsl" label="Enable SSL"
wire:model.live="enableSsl" instantSave="instantSaveSSL" canGate="update"
:canResource="$database" />
@else
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" disabled
<x-forms.checkbox id="enableSsl" label="Enable SSL"
wire:model.live="enableSsl" instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this settings." canGate="update"
:canResource="$database" />
@endif
</div>
@if ($database->enable_ssl)
@if ($enableSsl)
<div class="mx-2">
@if (str($database->status)->contains('exited'))
<x-forms.select id="database.ssl_mode" label="SSL Mode" wire:model.live="database.ssl_mode"
<x-forms.select id="sslMode" label="SSL Mode" wire:model.live="sslMode"
instantSave="instantSaveSSL"
helper="Choose the SSL verification mode for MongoDB connections" canGate="update"
:canResource="$database">
@ -115,7 +115,7 @@
</option>
</x-forms.select>
@else
<x-forms.select id="database.ssl_mode" label="SSL Mode" instantSave="instantSaveSSL"
<x-forms.select id="sslMode" label="SSL Mode" instantSave="instantSaveSSL"
disabled helper="Database should be stopped to change this settings." canGate="update"
:canResource="$database">
<option value="allow" title="Allow insecure connections">allow (insecure)</option>
@ -148,18 +148,18 @@
</x-slide-over>
@endif
</div>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available"
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available"
canGate="update" :canResource="$database" />
</div>
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" canGate="update" :canResource="$database" />
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}"
id="publicPort" label="Public Port" canGate="update" :canResource="$database" />
</div>
<x-forms.textarea label="Custom MongoDB Configuration" rows="10" id="database.mongo_conf"
<x-forms.textarea label="Custom MongoDB Configuration" rows="10" id="mongoConf"
canGate="update" :canResource="$database" />
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs"
instantSave="instantSaveAdvanced" id="isLogDrainEnabled" label="Drain Logs"
canGate="update" :canResource="$database" />
</div>
</div>

View file

@ -7,9 +7,9 @@
</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="database.name" canGate="update" :canResource="$database" />
<x-forms.input label="Description" id="database.description" canGate="update" :canResource="$database" />
<x-forms.input label="Image" id="database.image" required
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
<x-forms.input label="Description" id="description" canGate="update" :canResource="$database" />
<x-forms.input label="Image" id="image" required
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/mysql'>https://hub.docker.com/_/mysql</a>" canGate="update" :canResource="$database" />
</div>
<div class="pt-2 dark:text-warning">If you change the values in the database, please sync it here, otherwise
@ -17,29 +17,29 @@
</div>
@if ($database->started_at)
<div class="flex xl:flex-row flex-col gap-2">
<x-forms.input label="Root Password" id="database.mysql_root_password" type="password" required
<x-forms.input label="Root Password" id="mysqlRootPassword" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." canGate="update" :canResource="$database" />
<x-forms.input label="Normal User" id="database.mysql_user" required
<x-forms.input label="Normal User" id="mysqlUser" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." canGate="update" :canResource="$database" />
<x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required
<x-forms.input label="Normal User Password" id="mysqlPassword" type="password" required
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." canGate="update" :canResource="$database" />
</div>
<div class="flex flex-col gap-2">
<x-forms.input label="Initial Database" id="database.mysql_database"
<x-forms.input label="Initial Database" id="mysqlDatabase"
placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." canGate="update" :canResource="$database" />
</div>
@else
<div class="flex xl:flex-row flex-col gap-4 pb-2">
<x-forms.input label="Root Password" id="database.mysql_root_password" type="password"
<x-forms.input label="Root Password" id="mysqlRootPassword" type="password"
helper="You can only change this in the database." canGate="update" :canResource="$database" />
<x-forms.input label="Normal User" id="database.mysql_user" required
<x-forms.input label="Normal User" id="mysqlUser" required
helper="You can only change this in the database." canGate="update" :canResource="$database" />
<x-forms.input label="Normal User Password" id="database.mysql_password" type="password" required
<x-forms.input label="Normal User Password" id="mysqlPassword" type="password" required
helper="You can only change this in the database." canGate="update" :canResource="$database" />
</div>
<div class="flex flex-col gap-2">
<x-forms.input label="Initial Database" id="database.mysql_database"
<x-forms.input label="Initial Database" id="mysqlDatabase"
placeholder="If empty, it will be the same as Username."
helper="You can only change this in the database." canGate="update" :canResource="$database" />
</div>
@ -48,12 +48,12 @@
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" canGate="update" :canResource="$database" />
id="customDockerRunOptions" label="Custom Docker Options" canGate="update" :canResource="$database" />
</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"
<x-forms.input placeholder="3000:5432" id="portsMappings" 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 dark:text-warning'>Example</span>3000:5432,3002:5433" canGate="update" :canResource="$database" />
</div>
<x-forms.input label="MySQL URL (internal)"
@ -70,7 +70,7 @@
<div class="flex items-center justify-between py-2">
<div class="flex items-center justify-between w-full">
<h3>SSL Configuration</h3>
@if ($database->enable_ssl && $certificateValidUntil)
@if ($enableSsl && $certificateValidUntil)
<x-modal-confirmation title="Regenerate SSL Certificates"
buttonTitle="Regenerate SSL Certificates" :actions="[
'The SSL certificate of this database will be regenerated.',
@ -80,7 +80,7 @@
@endif
</div>
</div>
@if ($database->enable_ssl && $certificateValidUntil)
@if ($enableSsl && $certificateValidUntil)
<span class="text-sm">Valid until:
@if (now()->gt($certificateValidUntil))
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expired</span>
@ -97,18 +97,18 @@
<div class="flex flex-col gap-2">
<div class="w-64">
@if (str($database->status)->contains('exited'))
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" canGate="update" :canResource="$database" />
<x-forms.checkbox id="enableSsl" label="Enable SSL"
wire:model.live="enableSsl" instantSave="instantSaveSSL" canGate="update" :canResource="$database" />
@else
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" disabled
<x-forms.checkbox id="enableSsl" label="Enable SSL"
wire:model.live="enableSsl" instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this settings." />
@endif
</div>
@if ($database->enable_ssl)
@if ($enableSsl)
<div class="mx-2">
@if (str($database->status)->contains('exited'))
<x-forms.select id="database.ssl_mode" label="SSL Mode" wire:model.live="database.ssl_mode"
<x-forms.select id="sslMode" label="SSL Mode" wire:model.live="sslMode"
instantSave="instantSaveSSL"
helper="Choose the SSL verification mode for MySQL connections" canGate="update" :canResource="$database">
<option value="PREFERRED" title="Prefer secure connections">Prefer (secure)</option>
@ -118,7 +118,7 @@
</option>
</x-forms.select>
@else
<x-forms.select id="database.ssl_mode" label="SSL Mode" instantSave="instantSaveSSL"
<x-forms.select id="sslMode" label="SSL Mode" instantSave="instantSaveSSL"
disabled helper="Database should be stopped to change this settings.">
<option value="PREFERRED" title="Prefer secure connections">Prefer (secure)</option>
<option value="REQUIRED" title="Require secure connections">Require (secure)</option>
@ -151,16 +151,16 @@
</x-slide-over>
@endif
</div>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available" canGate="update" :canResource="$database" />
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available" canGate="update" :canResource="$database" />
</div>
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" canGate="update" :canResource="$database" />
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}"
id="publicPort" label="Public Port" canGate="update" :canResource="$database" />
</div>
<x-forms.textarea label="Custom Mysql Configuration" rows="10" id="database.mysql_conf" canGate="update" :canResource="$database" />
<x-forms.textarea label="Custom Mysql Configuration" rows="10" id="mysqlConf" canGate="update" :canResource="$database" />
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs" canGate="update" :canResource="$database" />
instantSave="instantSaveAdvanced" id="isLogDrainEnabled" label="Drain Logs" canGate="update" :canResource="$database" />
</div>
</form>
</div>

View file

@ -21,9 +21,9 @@
</x-forms.button>
</div>
<div class="flex flex-wrap gap-2 sm:flex-nowrap">
<x-forms.input label="Name" id="database.name" canGate="update" :canResource="$database" />
<x-forms.input label="Description" id="database.description" canGate="update" :canResource="$database" />
<x-forms.input label="Image" id="database.image" required canGate="update" :canResource="$database"
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
<x-forms.input label="Description" id="description" canGate="update" :canResource="$database" />
<x-forms.input label="Image" id="image" required canGate="update" :canResource="$database"
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="pt-2 dark:text-warning">If you change the values in the database, please sync it here, otherwise
@ -31,40 +31,40 @@
</div>
@if ($database->started_at)
<div class="flex xl:flex-row flex-col gap-2">
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres"
<x-forms.input label="Username" id="postgresUser" placeholder="If empty: postgres"
canGate="update" :canResource="$database"
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Password" id="database.postgres_password" type="password" required
<x-forms.input label="Password" id="postgresPassword" type="password" required
canGate="update" :canResource="$database"
helper="If you change this in the database, please sync it here, otherwise automations (like backups) won't work." />
<x-forms.input label="Initial Database" id="database.postgres_db"
<x-forms.input label="Initial Database" id="postgresDb"
placeholder="If empty, it will be the same as Username." readonly
helper="You can only change this in the database." />
</div>
@else
<div class="flex xl:flex-row flex-col gap-2 pb-2">
<x-forms.input label="Username" id="database.postgres_user" placeholder="If empty: postgres"
<x-forms.input label="Username" id="postgresUser" placeholder="If empty: postgres"
canGate="update" :canResource="$database" />
<x-forms.input label="Password" id="database.postgres_password" type="password" required
<x-forms.input label="Password" id="postgresPassword" type="password" required
canGate="update" :canResource="$database" />
<x-forms.input label="Initial Database" id="database.postgres_db"
<x-forms.input label="Initial Database" id="postgresDb"
placeholder="If empty, it will be the same as Username." canGate="update" :canResource="$database" />
</div>
@endif
<div class="flex gap-2">
<x-forms.input label="Initial Database Arguments" canGate="update" :canResource="$database"
id="database.postgres_initdb_args" placeholder="If empty, use default. See in docker docs." />
id="postgresInitdbArgs" placeholder="If empty, use default. See in docker docs." />
<x-forms.input label="Host Auth Method" canGate="update" :canResource="$database"
id="database.postgres_host_auth_method" placeholder="If empty, use default. See in docker docs." />
id="postgresHostAuthMethod" placeholder="If empty, use default. See in docker docs." />
</div>
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" canGate="update" :canResource="$database" />
id="customDockerRunOptions" label="Custom Docker Options" canGate="update" :canResource="$database" />
<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"
<x-forms.input placeholder="3000:5432" id="portsMappings" 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 dark:text-warning'>Example</span>3000:5432,3002:5433"
canGate="update" :canResource="$database" />
</div>
@ -81,7 +81,7 @@
<div class="flex flex-col gap-2">
<div class="flex items-center gap-2 py-2">
<h3>SSL Configuration</h3>
@if ($database->enable_ssl && $certificateValidUntil)
@if ($enableSsl && $certificateValidUntil)
<x-modal-confirmation title="Regenerate SSL Certificates" buttonTitle="Regenerate SSL Certificates"
:actions="[
'The SSL certificate of this database will be regenerated.',
@ -90,7 +90,7 @@
:confirmWithPassword="false" />
@endif
</div>
@if ($database->enable_ssl && $certificateValidUntil)
@if ($enableSsl && $certificateValidUntil)
<span class="text-sm">Valid until:
@if (now()->gt($certificateValidUntil))
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expired</span>
@ -107,20 +107,20 @@
<div class="flex flex-col gap-2">
<div class="w-64" wire:key='enable_ssl'>
@if ($database->isExited())
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" canGate="update"
<x-forms.checkbox id="enableSsl" label="Enable SSL"
wire:model.live="enableSsl" instantSave="instantSaveSSL" canGate="update"
:canResource="$database" />
@else
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" disabled
<x-forms.checkbox id="enableSsl" label="Enable SSL"
wire:model.live="enableSsl" instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this settings." />
@endif
</div>
@if ($database->enable_ssl)
@if ($enableSsl)
<div class="mx-2">
@if ($database->isExited())
<x-forms.select id="database.ssl_mode" label="SSL Mode"
wire:model.live="database.ssl_mode" instantSave="instantSaveSSL"
<x-forms.select id="sslMode" label="SSL Mode"
wire:model.live="sslMode" instantSave="instantSaveSSL"
helper="Choose the SSL verification mode for PostgreSQL connections" canGate="update"
:canResource="$database">
<option value="allow" title="Allow insecure connections">allow (insecure)</option>
@ -131,7 +131,7 @@
</option>
</x-forms.select>
@else
<x-forms.select id="database.ssl_mode" label="SSL Mode" instantSave="instantSaveSSL"
<x-forms.select id="sslMode" label="SSL Mode" instantSave="instantSaveSSL"
disabled helper="Database should be stopped to change this settings.">
<option value="allow" title="Allow insecure connections">allow (insecure)</option>
<option value="prefer" title="Prefer secure connections">prefer (secure)</option>
@ -161,16 +161,16 @@
@endif
</div>
<div class="flex flex-col gap-2 w-64">
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available"
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available"
canGate="update" :canResource="$database" />
</div>
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" canGate="update" :canResource="$database" />
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}"
id="publicPort" label="Public Port" canGate="update" :canResource="$database" />
</div>
<div class="flex flex-col gap-2">
<x-forms.textarea label="Custom PostgreSQL Configuration" rows="10"
id="database.postgres_conf" canGate="update" :canResource="$database" />
id="postgresConf" canGate="update" :canResource="$database" />
</div>
</form>
@ -178,7 +178,7 @@
<h3>Advanced</h3>
<div class="flex flex-col">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs"
instantSave="instantSaveAdvanced" id="isLogDrainEnabled" label="Drain Logs"
canGate="update" :canResource="$database" />
</div>
@ -201,7 +201,7 @@
@endcan
</div>
<div class="flex flex-col gap-2">
@forelse(data_get($database,'init_scripts', []) as $script)
@forelse($initScripts ?? [] as $script)
<livewire:project.database.init-script :script="$script" :wire:key="$script['index']" />
@empty
<div>No initialization scripts found.</div>

View file

@ -7,9 +7,9 @@
</x-forms.button>
</div>
<div class="flex gap-2">
<x-forms.input label="Name" id="database.name" canGate="update" :canResource="$database" />
<x-forms.input label="Description" id="database.description" canGate="update" :canResource="$database" />
<x-forms.input label="Image" id="database.image" required canGate="update" :canResource="$database"
<x-forms.input label="Name" id="name" canGate="update" :canResource="$database" />
<x-forms.input label="Description" id="description" canGate="update" :canResource="$database" />
<x-forms.input label="Image" id="image" required canGate="update" :canResource="$database"
helper="For all available images, check here:<br><br><a target='_blank' href='https://hub.docker.com/_/redis'>https://hub.docker.com/_/redis</a>" />
</div>
<div class="flex flex-col gap-2">
@ -19,19 +19,19 @@
automations won't work. <br>Changing them here will not change the values in the database.
</div>
<div class="flex gap-2">
@if (version_compare($redis_version, '6.0', '>='))
<x-forms.input label="Username" id="redis_username"
@if (version_compare($redisVersion, '6.0', '>='))
<x-forms.input label="Username" id="redisUsername"
helper="You can only change this in the database." canGate="update" :canResource="$database" />
@endif
<x-forms.input label="Password" id="redis_password" type="password"
<x-forms.input label="Password" id="redisPassword" type="password"
helper="You can only change this in the database." canGate="update" :canResource="$database" />
</div>
@else
<div class="pt-2 dark:text-warning">You can only change the username and password in the database after
initial start.</div>
<div class="flex gap-2">
@if (version_compare($redis_version, '6.0', '>='))
<x-forms.input label="Username" id="redis_username" required
@if (version_compare($redisVersion, '6.0', '>='))
<x-forms.input label="Username" id="redisUsername" required
helper="You can change the Redis Username in the input field below or by editing the value of the REDIS_USERNAME environment variable.
<br><br>
If you change the Redis Username in the database, please sync it here, otherwise automations (like backups) won't work.
@ -39,7 +39,7 @@
Note: If the environment variable REDIS_USERNAME is set as a shared variable (environment, project, or team-based), this input field will become read-only."
:disabled="$this->isSharedVariable('REDIS_USERNAME')" canGate="update" :canResource="$database" />
@endif
<x-forms.input label="Password" id="redis_password" type="password" required
<x-forms.input label="Password" id="redisPassword" type="password" required
helper="You can change the Redis Password in the input field below or by editing the value of the REDIS_PASSWORD environment variable.
<br><br>
If you change the Redis Password in the database, please sync it here, otherwise automations (like backups) won't work.
@ -52,28 +52,28 @@
<x-forms.input
helper="You can add custom docker run options that will be used when your container is started.<br>Note: Not all options are supported, as they could mess up Coolify's automation and could cause bad experience for users.<br><br>Check the <a class='underline dark:text-white' href='https://coolify.io/docs/knowledge-base/docker/custom-commands'>docs.</a>"
placeholder="--cap-add SYS_ADMIN --device=/dev/fuse --security-opt apparmor:unconfined --ulimit nofile=1024:1024 --tmpfs /run:rw,noexec,nosuid,size=65536k"
id="database.custom_docker_run_options" label="Custom Docker Options" canGate="update" :canResource="$database" />
id="customDockerRunOptions" label="Custom Docker Options" canGate="update" :canResource="$database" />
<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"
<x-forms.input placeholder="3000:5432" id="portsMappings" 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 dark:text-warning'>Example</span>3000:5432,3002:5433"
canGate="update" :canResource="$database" />
</div>
<x-forms.input label="Redis URL (internal)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url" canGate="update" :canResource="$database" />
@if ($db_url_public)
type="password" readonly wire:model="dbUrl" canGate="update" :canResource="$database" />
@if ($dbUrlPublic)
<x-forms.input label="Redis URL (public)"
helper="If you change the user/password/port, this could be different. This is with the default values."
type="password" readonly wire:model="db_url_public" canGate="update" :canResource="$database" />
type="password" readonly wire:model="dbUrlPublic" canGate="update" :canResource="$database" />
@endif
</div>
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between py-2">
<div class="flex items-center justify-between w-full">
<h3>SSL Configuration</h3>
@if ($database->enable_ssl && $certificateValidUntil)
@if ($enableSsl && $certificateValidUntil)
<x-modal-confirmation title="Regenerate SSL Certificates"
buttonTitle="Regenerate SSL Certificates" :actions="[
'The SSL certificate of this database will be regenerated.',
@ -83,7 +83,7 @@
@endif
</div>
</div>
@if ($database->enable_ssl && $certificateValidUntil)
@if ($enableSsl && $certificateValidUntil)
<span class="text-sm">Valid until:
@if (now()->gt($certificateValidUntil))
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expired</span>
@ -98,12 +98,12 @@
<div class="flex flex-col gap-2">
<div class="w-64" wire:key='enable_ssl'>
@if (str($database->status)->contains('exited'))
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" canGate="update"
<x-forms.checkbox id="enableSsl" label="Enable SSL"
wire:model.live="enableSsl" instantSave="instantSaveSSL" canGate="update"
:canResource="$database" />
@else
<x-forms.checkbox id="database.enable_ssl" label="Enable SSL"
wire:model.live="database.enable_ssl" instantSave="instantSaveSSL" disabled
<x-forms.checkbox id="enableSsl" label="Enable SSL"
wire:model.live="enableSsl" instantSave="instantSaveSSL" disabled
helper="Database should be stopped to change this settings." canGate="update"
:canResource="$database" />
@endif
@ -117,23 +117,23 @@
<h3>Proxy</h3>
<x-loading wire:loading wire:target="instantSave" />
</div>
@if (data_get($database, 'is_public'))
@if ($isPublic)
<x-slide-over fullScreen>
<x-slot:title>Proxy Logs</x-slot:title>
<x-slot:content>
<livewire:project.shared.get-logs :server="$server" :resource="$database"
container="{{ data_get($database, 'uuid') }}-proxy" lazy />
</x-slot:content>
<x-forms.button disabled="{{ !data_get($database, 'is_public') }}"
<x-forms.button disabled="{{ !$isPublic }}"
@click="slideOverOpen=true">Logs</x-forms.button>
</x-slide-over>
@endif
</div>
<x-forms.checkbox instantSave id="database.is_public" label="Make it publicly available"
<x-forms.checkbox instantSave id="isPublic" label="Make it publicly available"
canGate="update" :canResource="$database" />
</div>
<x-forms.input placeholder="5432" disabled="{{ data_get($database, 'is_public') }}"
id="database.public_port" label="Public Port" canGate="update" :canResource="$database" />
<x-forms.input placeholder="5432" disabled="{{ $isPublic }}"
id="publicPort" label="Public Port" canGate="update" :canResource="$database" />
</div>
<x-forms.textarea placeholder="# maxmemory 256mb
# maxmemory-policy allkeys-lru
@ -141,7 +141,7 @@
helper="You only need to provide the Redis directives you want to override — Redis will use default values for everything else. <br/><br/>
⚠️ <strong>Important:</strong> Coolify automatically applies the requirepass directive using the password shown in the Password field above. If you override requirepass in your custom configuration, make sure it matches the password field to avoid authentication issues. <br/><br/>
🔗 <strong>Tip:</strong> <a target='_blank' class='underline dark:text-white' href='https://raw.githubusercontent.com/redis/redis/7.2/redis.conf'>View the full Redis default configuration</a> to see what options are available."
label="Custom Redis Configuration" rows="10" id="database.redis_conf" canGate="update"
label="Custom Redis Configuration" rows="10" id="redisConf" canGate="update"
:canResource="$database" />
@ -149,7 +149,7 @@
<h3 class="pt-4">Advanced</h3>
<div class="flex flex-col">
<x-forms.checkbox helper="Drain logs to your configured log drain endpoint in your Server settings."
instantSave="instantSaveAdvanced" id="database.is_log_drain_enabled" label="Drain Logs"
instantSave="instantSaveAdvanced" id="isLogDrainEnabled" label="Drain Logs"
canGate="update" :canResource="$database" />
</div>