diff --git a/app/Livewire/Project/Application/Configuration.php b/app/Livewire/Project/Application/Configuration.php
index cc1bf15b9..887fff35a 100644
--- a/app/Livewire/Project/Application/Configuration.php
+++ b/app/Livewire/Project/Application/Configuration.php
@@ -17,17 +17,10 @@ class Configuration extends Component
public $servers;
- public function getListeners()
- {
- $teamId = auth()->user()->currentTeam()->id;
-
- return [
- "echo-private:team.{$teamId},ServiceChecked" => '$refresh',
- "echo-private:team.{$teamId},ServiceStatusChanged" => '$refresh',
- 'buildPackUpdated' => '$refresh',
- 'refresh' => '$refresh',
- ];
- }
+ protected $listeners = [
+ 'buildPackUpdated' => '$refresh',
+ 'refresh' => '$refresh',
+ ];
public function mount()
{
@@ -51,8 +44,6 @@ public function mount()
$this->environment = $environment;
$this->application = $application;
-
-
if ($this->application->build_pack === 'dockercompose' && $this->currentRoute === 'project.application.healthcheck') {
return redirect()->route('project.application.configuration', ['project_uuid' => $project->uuid, 'environment_uuid' => $environment->uuid, 'application_uuid' => $application->uuid]);
}
diff --git a/app/Livewire/Project/Database/Clickhouse/General.php b/app/Livewire/Project/Database/Clickhouse/General.php
index 2583c10ea..edcb31f5e 100644
--- a/app/Livewire/Project/Database/Clickhouse/General.php
+++ b/app/Livewire/Project/Database/Clickhouse/General.php
@@ -48,13 +48,26 @@ class General extends Component
public function getListeners()
{
+ $userId = Auth::id();
$teamId = Auth::user()->currentTeam()->id;
return [
"echo-private:team.{$teamId},DatabaseProxyStopped" => 'databaseProxyStopped',
+ // Broadcasts go to refreshStatus, which only writes display-only properties.
+ // Never wire status broadcasts to a handler that touches text-input properties —
+ // it clobbers in-progress typing every 10s. See coolify#6062 / #6354 / #9695.
+ "echo-private:user.{$userId},DatabaseStatusChanged" => 'refreshStatus',
+ "echo-private:team.{$teamId},ServiceChecked" => 'refreshStatus',
];
}
+ public function refreshStatus(): void
+ {
+ $this->database->refresh();
+ $this->dbUrl = $this->database->internal_db_url;
+ $this->dbUrlPublic = $this->database->external_db_url;
+ }
+
public function mount()
{
try {
diff --git a/app/Livewire/Project/Database/Configuration.php b/app/Livewire/Project/Database/Configuration.php
index 7c64a6eef..9f952ff2b 100644
--- a/app/Livewire/Project/Database/Configuration.php
+++ b/app/Livewire/Project/Database/Configuration.php
@@ -2,8 +2,9 @@
namespace App\Livewire\Project\Database;
-use Auth;
+use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
+use Illuminate\Support\ItemNotFoundException;
use Livewire\Component;
class Configuration extends Component
@@ -18,15 +19,6 @@ class Configuration extends Component
public $environment;
- public function getListeners()
- {
- $teamId = Auth::user()->currentTeam()->id;
-
- return [
- "echo-private:team.{$teamId},ServiceChecked" => '$refresh',
- ];
- }
-
public function mount()
{
try {
@@ -55,10 +47,10 @@ public function mount()
$this->dispatch('configurationChanged');
}
} catch (\Throwable $e) {
- if ($e instanceof \Illuminate\Auth\Access\AuthorizationException) {
+ if ($e instanceof AuthorizationException) {
return redirect()->route('dashboard');
}
- if ($e instanceof \Illuminate\Support\ItemNotFoundException) {
+ if ($e instanceof ItemNotFoundException) {
return redirect()->route('dashboard');
}
diff --git a/app/Livewire/Project/Database/Dragonfly/General.php b/app/Livewire/Project/Database/Dragonfly/General.php
index 9e1ea0d10..ae8ec9476 100644
--- a/app/Livewire/Project/Database/Dragonfly/General.php
+++ b/app/Livewire/Project/Database/Dragonfly/General.php
@@ -57,11 +57,22 @@ public function getListeners()
return [
"echo-private:team.{$teamId},DatabaseProxyStopped" => 'databaseProxyStopped',
- "echo-private:user.{$userId},DatabaseStatusChanged" => 'refresh',
- "echo-private:team.{$teamId},ServiceChecked" => 'refresh',
+ // Broadcasts go to refreshStatus, which only writes display-only properties.
+ // Never wire status broadcasts to a handler that touches text-input properties —
+ // it clobbers in-progress typing every 10s. See coolify#6062 / #6354 / #9695.
+ "echo-private:user.{$userId},DatabaseStatusChanged" => 'refreshStatus',
+ "echo-private:team.{$teamId},ServiceChecked" => 'refreshStatus',
];
}
+ public function refreshStatus(): void
+ {
+ $this->database->refresh();
+ $this->dbUrl = $this->database->internal_db_url;
+ $this->dbUrlPublic = $this->database->external_db_url;
+ $this->certificateValidUntil = $this->database->sslCertificates()->first()?->valid_until;
+ }
+
public function mount()
{
try {
diff --git a/app/Livewire/Project/Database/Import.php b/app/Livewire/Project/Database/Import.php
index 1cdc681cd..0c19709a5 100644
--- a/app/Livewire/Project/Database/Import.php
+++ b/app/Livewire/Project/Database/Import.php
@@ -5,9 +5,17 @@
use App\Models\S3Storage;
use App\Models\Server;
use App\Models\Service;
+use App\Models\ServiceDatabase;
+use App\Models\StandaloneClickhouse;
+use App\Models\StandaloneDragonfly;
+use App\Models\StandaloneKeydb;
+use App\Models\StandaloneMariadb;
+use App\Models\StandaloneMongodb;
+use App\Models\StandaloneMysql;
+use App\Models\StandalonePostgresql;
+use App\Models\StandaloneRedis;
use App\Support\ValidationPatterns;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
-use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Storage;
use Livewire\Attributes\Computed;
use Livewire\Attributes\Locked;
@@ -192,15 +200,9 @@ public function server()
return Server::ownedByCurrentTeam()->find($this->serverId);
}
- public function getListeners()
- {
- $userId = Auth::id();
-
- return [
- "echo-private:user.{$userId},DatabaseStatusChanged" => '$refresh',
- 'slideOverClosed' => 'resetActivityId',
- ];
- }
+ protected $listeners = [
+ 'slideOverClosed' => 'resetActivityId',
+ ];
public function resetActivityId()
{
@@ -219,7 +221,7 @@ public function updatedDumpAll($value)
$morphClass = $this->resource->getMorphClass();
// Handle ServiceDatabase by checking the database type
- if ($morphClass === \App\Models\ServiceDatabase::class) {
+ if ($morphClass === ServiceDatabase::class) {
$dbType = $this->resource->databaseType();
if (str_contains($dbType, 'mysql')) {
$morphClass = 'mysql';
@@ -231,7 +233,7 @@ public function updatedDumpAll($value)
}
switch ($morphClass) {
- case \App\Models\StandaloneMariadb::class:
+ case StandaloneMariadb::class:
case 'mariadb':
if ($value === true) {
$this->mariadbRestoreCommand = <<<'EOD'
@@ -247,7 +249,7 @@ public function updatedDumpAll($value)
$this->mariadbRestoreCommand = 'mariadb -u $MARIADB_USER -p$MARIADB_PASSWORD $MARIADB_DATABASE';
}
break;
- case \App\Models\StandaloneMysql::class:
+ case StandaloneMysql::class:
case 'mysql':
if ($value === true) {
$this->mysqlRestoreCommand = <<<'EOD'
@@ -263,7 +265,7 @@ public function updatedDumpAll($value)
$this->mysqlRestoreCommand = 'mysql -u $MYSQL_USER -p$MYSQL_PASSWORD $MYSQL_DATABASE';
}
break;
- case \App\Models\StandalonePostgresql::class:
+ case StandalonePostgresql::class:
case 'postgresql':
if ($value === true) {
$this->postgresqlRestoreCommand = <<<'EOD'
@@ -321,7 +323,7 @@ public function getContainers()
$this->resourceStatus = $resource->status ?? '';
// Handle ServiceDatabase server access differently
- if ($resource->getMorphClass() === \App\Models\ServiceDatabase::class) {
+ if ($resource->getMorphClass() === ServiceDatabase::class) {
$server = $resource->service?->server;
if (! $server) {
abort(404, 'Server not found for this service database.');
@@ -359,16 +361,16 @@ public function getContainers()
}
if (
- $resource->getMorphClass() === \App\Models\StandaloneRedis::class ||
- $resource->getMorphClass() === \App\Models\StandaloneKeydb::class ||
- $resource->getMorphClass() === \App\Models\StandaloneDragonfly::class ||
- $resource->getMorphClass() === \App\Models\StandaloneClickhouse::class
+ $resource->getMorphClass() === StandaloneRedis::class ||
+ $resource->getMorphClass() === StandaloneKeydb::class ||
+ $resource->getMorphClass() === StandaloneDragonfly::class ||
+ $resource->getMorphClass() === StandaloneClickhouse::class
) {
$this->unsupported = true;
}
// Mark unsupported ServiceDatabase types (Redis, KeyDB, etc.)
- if ($resource->getMorphClass() === \App\Models\ServiceDatabase::class) {
+ if ($resource->getMorphClass() === ServiceDatabase::class) {
$dbType = $resource->databaseType();
if (str_contains($dbType, 'redis') || str_contains($dbType, 'keydb') ||
str_contains($dbType, 'dragonfly') || str_contains($dbType, 'clickhouse')) {
@@ -664,7 +666,7 @@ public function restoreFromS3(string $password = ''): bool|string
$fullImageName = "{$helperImage}:{$latestVersion}";
// Get the database destination network
- if ($this->resource->getMorphClass() === \App\Models\ServiceDatabase::class) {
+ if ($this->resource->getMorphClass() === ServiceDatabase::class) {
$destinationNetwork = $this->resource->service->destination->network ?? 'coolify';
} else {
$destinationNetwork = $this->resource->destination->network ?? 'coolify';
@@ -756,7 +758,7 @@ public function buildRestoreCommand(string $tmpPath): string
$morphClass = $this->resource->getMorphClass();
// Handle ServiceDatabase by checking the database type
- if ($morphClass === \App\Models\ServiceDatabase::class) {
+ if ($morphClass === ServiceDatabase::class) {
$dbType = $this->resource->databaseType();
if (str_contains($dbType, 'mysql')) {
$morphClass = 'mysql';
@@ -770,7 +772,7 @@ public function buildRestoreCommand(string $tmpPath): string
}
switch ($morphClass) {
- case \App\Models\StandaloneMariadb::class:
+ case StandaloneMariadb::class:
case 'mariadb':
$restoreCommand = $this->mariadbRestoreCommand;
if ($this->dumpAll) {
@@ -779,7 +781,7 @@ public function buildRestoreCommand(string $tmpPath): string
$restoreCommand .= " < {$tmpPath}";
}
break;
- case \App\Models\StandaloneMysql::class:
+ case StandaloneMysql::class:
case 'mysql':
$restoreCommand = $this->mysqlRestoreCommand;
if ($this->dumpAll) {
@@ -788,7 +790,7 @@ public function buildRestoreCommand(string $tmpPath): string
$restoreCommand .= " < {$tmpPath}";
}
break;
- case \App\Models\StandalonePostgresql::class:
+ case StandalonePostgresql::class:
case 'postgresql':
$restoreCommand = $this->postgresqlRestoreCommand;
if ($this->dumpAll) {
@@ -797,7 +799,7 @@ public function buildRestoreCommand(string $tmpPath): string
$restoreCommand .= " {$tmpPath}";
}
break;
- case \App\Models\StandaloneMongodb::class:
+ case StandaloneMongodb::class:
case 'mongodb':
$restoreCommand = $this->mongodbRestoreCommand;
if ($this->dumpAll === false) {
diff --git a/app/Livewire/Project/Database/Keydb/General.php b/app/Livewire/Project/Database/Keydb/General.php
index 7c8808499..0511c9d04 100644
--- a/app/Livewire/Project/Database/Keydb/General.php
+++ b/app/Livewire/Project/Database/Keydb/General.php
@@ -59,11 +59,22 @@ public function getListeners()
return [
"echo-private:team.{$teamId},DatabaseProxyStopped" => 'databaseProxyStopped',
- "echo-private:user.{$userId},DatabaseStatusChanged" => 'refresh',
- "echo-private:team.{$teamId},ServiceChecked" => 'refresh',
+ // Broadcasts go to refreshStatus, which only writes display-only properties.
+ // Never wire status broadcasts to a handler that touches text-input properties —
+ // it clobbers in-progress typing every 10s. See coolify#6062 / #6354 / #9695.
+ "echo-private:user.{$userId},DatabaseStatusChanged" => 'refreshStatus',
+ "echo-private:team.{$teamId},ServiceChecked" => 'refreshStatus',
];
}
+ public function refreshStatus(): void
+ {
+ $this->database->refresh();
+ $this->dbUrl = $this->database->internal_db_url;
+ $this->dbUrlPublic = $this->database->external_db_url;
+ $this->certificateValidUntil = $this->database->sslCertificates()->first()?->valid_until;
+ }
+
public function mount()
{
try {
diff --git a/app/Livewire/Project/Database/Mariadb/General.php b/app/Livewire/Project/Database/Mariadb/General.php
index ea6d902e7..edd02eb95 100644
--- a/app/Livewire/Project/Database/Mariadb/General.php
+++ b/app/Livewire/Project/Database/Mariadb/General.php
@@ -64,11 +64,22 @@ public function getListeners()
$teamId = Auth::user()->currentTeam()->id;
return [
- "echo-private:user.{$userId},DatabaseStatusChanged" => 'refresh',
- "echo-private:team.{$teamId},ServiceChecked" => 'refresh',
+ // Broadcasts go to refreshStatus, which only writes display-only properties.
+ // Never wire status broadcasts to a handler that touches text-input properties —
+ // it clobbers in-progress typing every 10s. See coolify#6062 / #6354 / #9695.
+ "echo-private:user.{$userId},DatabaseStatusChanged" => 'refreshStatus',
+ "echo-private:team.{$teamId},ServiceChecked" => 'refreshStatus',
];
}
+ public function refreshStatus(): void
+ {
+ $this->database->refresh();
+ $this->db_url = $this->database->internal_db_url;
+ $this->db_url_public = $this->database->external_db_url;
+ $this->certificateValidUntil = $this->database->sslCertificates()->first()?->valid_until;
+ }
+
protected function rules(): array
{
return [
diff --git a/app/Livewire/Project/Database/Mongodb/General.php b/app/Livewire/Project/Database/Mongodb/General.php
index 3af4b0b2a..1b5a62d2f 100644
--- a/app/Livewire/Project/Database/Mongodb/General.php
+++ b/app/Livewire/Project/Database/Mongodb/General.php
@@ -64,11 +64,22 @@ public function getListeners()
$teamId = Auth::user()->currentTeam()->id;
return [
- "echo-private:user.{$userId},DatabaseStatusChanged" => 'refresh',
- "echo-private:team.{$teamId},ServiceChecked" => 'refresh',
+ // Broadcasts go to refreshStatus, which only writes display-only properties.
+ // Never wire status broadcasts to a handler that touches text-input properties —
+ // it clobbers in-progress typing every 10s. See coolify#6062 / #6354 / #9695.
+ "echo-private:user.{$userId},DatabaseStatusChanged" => 'refreshStatus',
+ "echo-private:team.{$teamId},ServiceChecked" => 'refreshStatus',
];
}
+ public function refreshStatus(): void
+ {
+ $this->database->refresh();
+ $this->db_url = $this->database->internal_db_url;
+ $this->db_url_public = $this->database->external_db_url;
+ $this->certificateValidUntil = $this->database->sslCertificates()->first()?->valid_until;
+ }
+
protected function rules(): array
{
return [
diff --git a/app/Livewire/Project/Database/Mysql/General.php b/app/Livewire/Project/Database/Mysql/General.php
index 34726bd0a..6e1e55b3f 100644
--- a/app/Livewire/Project/Database/Mysql/General.php
+++ b/app/Livewire/Project/Database/Mysql/General.php
@@ -66,11 +66,22 @@ public function getListeners()
$teamId = Auth::user()->currentTeam()->id;
return [
- "echo-private:user.{$userId},DatabaseStatusChanged" => 'refresh',
- "echo-private:team.{$teamId},ServiceChecked" => 'refresh',
+ // Broadcasts go to refreshStatus, which only writes display-only properties.
+ // Never wire status broadcasts to a handler that touches text-input properties —
+ // it clobbers in-progress typing every 10s. See coolify#6062 / #6354 / #9695.
+ "echo-private:user.{$userId},DatabaseStatusChanged" => 'refreshStatus',
+ "echo-private:team.{$teamId},ServiceChecked" => 'refreshStatus',
];
}
+ public function refreshStatus(): void
+ {
+ $this->database->refresh();
+ $this->db_url = $this->database->internal_db_url;
+ $this->db_url_public = $this->database->external_db_url;
+ $this->certificateValidUntil = $this->database->sslCertificates()->first()?->valid_until;
+ }
+
protected function rules(): array
{
return [
diff --git a/app/Livewire/Project/Database/Postgresql/General.php b/app/Livewire/Project/Database/Postgresql/General.php
index b5fb85483..1b36ac28a 100644
--- a/app/Livewire/Project/Database/Postgresql/General.php
+++ b/app/Livewire/Project/Database/Postgresql/General.php
@@ -74,13 +74,24 @@ public function getListeners()
$teamId = Auth::user()->currentTeam()->id;
return [
- "echo-private:user.{$userId},DatabaseStatusChanged" => 'refresh',
- "echo-private:team.{$teamId},ServiceChecked" => 'refresh',
+ // Broadcasts go to refreshStatus, which only writes display-only properties.
+ // Never wire status broadcasts to a handler that touches text-input properties —
+ // it clobbers in-progress typing every 10s. See coolify#6062 / #6354 / #9695.
+ "echo-private:user.{$userId},DatabaseStatusChanged" => 'refreshStatus',
+ "echo-private:team.{$teamId},ServiceChecked" => 'refreshStatus',
'save_init_script',
'delete_init_script',
];
}
+ public function refreshStatus(): void
+ {
+ $this->database->refresh();
+ $this->db_url = $this->database->internal_db_url;
+ $this->db_url_public = $this->database->external_db_url;
+ $this->certificateValidUntil = $this->database->sslCertificates()->first()?->valid_until;
+ }
+
protected function rules(): array
{
return [
diff --git a/app/Livewire/Project/Database/Redis/General.php b/app/Livewire/Project/Database/Redis/General.php
index c3cc43972..aff7b7afa 100644
--- a/app/Livewire/Project/Database/Redis/General.php
+++ b/app/Livewire/Project/Database/Redis/General.php
@@ -4,14 +4,11 @@
use App\Actions\Database\StartDatabaseProxy;
use App\Actions\Database\StopDatabaseProxy;
-use App\Helpers\SslHelper;
use App\Models\Server;
use App\Models\StandaloneRedis;
use App\Support\ValidationPatterns;
-use Carbon\Carbon;
use Exception;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
-use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class General extends Component
@@ -48,25 +45,9 @@ class General extends Component
public string $redisVersion;
- public ?string $dbUrl = null;
-
- public ?string $dbUrlPublic = null;
-
- public bool $enableSsl = false;
-
- public ?Carbon $certificateValidUntil = null;
-
- public function getListeners()
- {
- $userId = Auth::id();
- $teamId = Auth::user()->currentTeam()->id;
-
- return [
- "echo-private:user.{$userId},DatabaseStatusChanged" => 'refresh',
- "echo-private:team.{$teamId},ServiceChecked" => 'refresh',
- 'envsUpdated' => 'refresh',
- ];
- }
+ protected $listeners = [
+ 'envsUpdated' => 'refresh',
+ ];
protected function rules(): array
{
@@ -87,7 +68,6 @@ protected function rules(): array
'redisPassword' => ValidationPatterns::databasePasswordRules(
enforcePattern: $this->redisPassword !== $this->database->redis_password,
),
- 'enableSsl' => 'boolean',
];
}
@@ -122,7 +102,6 @@ protected function messages(): array
'customDockerRunOptions' => 'Custom Docker Options',
'redisUsername' => 'Redis Username',
'redisPassword' => 'Redis Password',
- 'enableSsl' => 'Enable SSL',
];
public function mount()
@@ -136,12 +115,6 @@ public function mount()
return;
}
-
- $existingCert = $this->database->sslCertificates()->first();
-
- if ($existingCert) {
- $this->certificateValidUntil = $existingCert->valid_until;
- }
} catch (\Throwable $e) {
return handleError($e, $this);
}
@@ -161,11 +134,7 @@ public function syncData(bool $toModel = false)
$this->database->public_port_timeout = $this->publicPortTimeout ?: null;
$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;
@@ -177,9 +146,6 @@ public function syncData(bool $toModel = false)
$this->publicPortTimeout = $this->database->public_port_timeout;
$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;
@@ -227,6 +193,7 @@ public function submit()
);
$this->dispatch('success', 'Database updated.');
+ $this->dispatch('databaseUpdated');
} catch (Exception $e) {
return handleError($e, $this);
} finally {
@@ -259,6 +226,7 @@ public function instantSave()
StopDatabaseProxy::run($this->database);
$this->dispatch('success', 'Database is no longer publicly accessible.');
}
+ $this->dispatch('databaseUpdated');
} catch (\Throwable $e) {
$this->isPublic = ! $this->isPublic;
$this->syncData(true);
@@ -267,63 +235,6 @@ public function instantSave()
}
}
- public function instantSaveSSL()
- {
- try {
- $this->authorize('update', $this->database);
-
- $this->syncData(true);
- $this->dispatch('success', 'SSL configuration updated.');
- } catch (Exception $e) {
- return handleError($e, $this);
- }
- }
-
- public function regenerateSslCertificate()
- {
- try {
- $this->authorize('update', $this->database);
-
- $existingCert = $this->database->sslCertificates()->first();
-
- if (! $existingCert) {
- $this->dispatch('error', 'No existing SSL certificate found for this database.');
-
- return;
- }
-
- $caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first();
-
- if (! $caCert) {
- $this->server->generateCaCertificate();
- $caCert = $this->server->sslCertificates()->where('is_ca_certificate', true)->first();
- }
-
- if (! $caCert) {
- $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
-
- return;
- }
-
- SslHelper::generateSslCertificate(
- commonName: $existingCert->common_name,
- subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
- resourceType: $existingCert->resource_type,
- resourceId: $existingCert->resource_id,
- serverId: $existingCert->server_id,
- caCert: $caCert->ssl_certificate,
- caKey: $caCert->ssl_private_key,
- configurationDir: $existingCert->configuration_dir,
- mountPath: $existingCert->mount_path,
- isPemKeyFileRequired: true,
- );
-
- $this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.');
- } catch (Exception $e) {
- handleError($e, $this);
- }
- }
-
public function refresh(): void
{
$this->database->refresh();
diff --git a/app/Livewire/Project/Database/Redis/StatusInfo.php b/app/Livewire/Project/Database/Redis/StatusInfo.php
new file mode 100644
index 000000000..183ed936f
--- /dev/null
+++ b/app/Livewire/Project/Database/Redis/StatusInfo.php
@@ -0,0 +1,116 @@
+currentTeam()->id;
+
+ return [
+ "echo-private:user.{$userId},DatabaseStatusChanged" => 'refresh',
+ "echo-private:team.{$teamId},ServiceChecked" => 'refresh',
+ 'databaseUpdated' => 'refresh',
+ ];
+ }
+
+ public function mount(): void
+ {
+ $this->refresh();
+ }
+
+ public function refresh(): void
+ {
+ $this->database->refresh();
+ $this->enableSsl = (bool) $this->database->enable_ssl;
+ $this->dbUrl = $this->database->internal_db_url;
+ $this->dbUrlPublic = $this->database->external_db_url;
+ $this->certificateValidUntil = $this->database->sslCertificates()->first()?->valid_until;
+ }
+
+ public function instantSaveSSL(): void
+ {
+ try {
+ $this->authorize('update', $this->database);
+ $this->database->enable_ssl = $this->enableSsl;
+ $this->database->save();
+ $this->dispatch('success', 'SSL configuration updated.');
+ } catch (Exception $e) {
+ handleError($e, $this);
+ }
+ }
+
+ public function regenerateSslCertificate(): void
+ {
+ try {
+ $this->authorize('update', $this->database);
+
+ $existingCert = $this->database->sslCertificates()->first();
+
+ if (! $existingCert) {
+ $this->dispatch('error', 'No existing SSL certificate found for this database.');
+
+ return;
+ }
+
+ $server = $this->database->destination->server;
+ $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first();
+
+ if (! $caCert) {
+ $server->generateCaCertificate();
+ $caCert = $server->sslCertificates()->where('is_ca_certificate', true)->first();
+ }
+
+ if (! $caCert) {
+ $this->dispatch('error', 'No CA certificate found for this database. Please generate a CA certificate for this server in the server/advanced page.');
+
+ return;
+ }
+
+ SslHelper::generateSslCertificate(
+ commonName: $existingCert->common_name,
+ subjectAlternativeNames: $existingCert->subject_alternative_names ?? [],
+ resourceType: $existingCert->resource_type,
+ resourceId: $existingCert->resource_id,
+ serverId: $existingCert->server_id,
+ caCert: $caCert->ssl_certificate,
+ caKey: $caCert->ssl_private_key,
+ configurationDir: $existingCert->configuration_dir,
+ mountPath: $existingCert->mount_path,
+ isPemKeyFileRequired: true,
+ );
+
+ $this->refresh();
+ $this->dispatch('success', 'SSL certificates regenerated. Restart database to apply changes.');
+ } catch (Exception $e) {
+ handleError($e, $this);
+ }
+ }
+
+ public function render()
+ {
+ return view('livewire.project.database.redis.status-info');
+ }
+}
diff --git a/app/Livewire/Project/Service/Configuration.php b/app/Livewire/Project/Service/Configuration.php
index 2d69ceb12..ac2b39bb8 100644
--- a/app/Livewire/Project/Service/Configuration.php
+++ b/app/Livewire/Project/Service/Configuration.php
@@ -4,7 +4,6 @@
use App\Models\Service;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
-use Illuminate\Support\Facades\Auth;
use Livewire\Component;
class Configuration extends Component
@@ -27,16 +26,10 @@ class Configuration extends Component
public array $parameters;
- public function getListeners()
- {
- $teamId = Auth::user()->currentTeam()->id;
-
- return [
- "echo-private:team.{$teamId},ServiceChecked" => 'serviceChecked',
- 'refreshServices' => 'refreshServices',
- 'refresh' => 'refreshServices',
- ];
- }
+ protected $listeners = [
+ 'refreshServices' => 'refreshServices',
+ 'refresh' => 'refreshServices',
+ ];
public function render()
{
@@ -105,18 +98,4 @@ public function restartDatabase($id)
return handleError($e, $this);
}
}
-
- public function serviceChecked()
- {
- try {
- $this->service->applications->each(function ($application) {
- $application->refresh();
- });
- $this->service->databases->each(function ($database) {
- $database->refresh();
- });
- } catch (\Exception $e) {
- return handleError($e, $this);
- }
- }
}
diff --git a/app/Livewire/Server/Sentinel.php b/app/Livewire/Server/Sentinel.php
index a4b35891b..06aebd8f8 100644
--- a/app/Livewire/Server/Sentinel.php
+++ b/app/Livewire/Server/Sentinel.php
@@ -93,7 +93,9 @@ public function handleSentinelRestarted($event)
{
if ($event['serverUuid'] === $this->server->uuid) {
$this->server->refresh();
- $this->syncData();
+ // Only refresh display-only state; never re-sync text-input properties
+ // (would clobber any unsaved typing — see coolify#6062 / #6354 / #9695).
+ $this->sentinelUpdatedAt = $this->server->sentinel_updated_at;
$this->dispatch('success', 'Sentinel has been restarted successfully.');
}
}
diff --git a/app/Livewire/Server/Show.php b/app/Livewire/Server/Show.php
index 3e05d9306..d7339dcdb 100644
--- a/app/Livewire/Server/Show.php
+++ b/app/Livewire/Server/Show.php
@@ -277,7 +277,9 @@ public function handleSentinelRestarted($event)
// Only refresh if the event is for this server
if (isset($event['serverUuid']) && $event['serverUuid'] === $this->server->uuid) {
$this->server->refresh();
- $this->syncData();
+ // Only refresh display-only state; never re-sync text-input properties
+ // (would clobber any unsaved typing — see coolify#6062 / #6354 / #9695).
+ $this->sentinelUpdatedAt = $this->server->sentinel_updated_at;
$this->dispatch('success', 'Sentinel has been restarted successfully.');
}
}
@@ -457,12 +459,15 @@ public function handleServerValidated($event = null)
return;
}
- // Refresh server data
+ // Refresh server data and only the display-only state that validation produces.
+ // Never re-sync text-input properties via syncData() — would clobber any
+ // unsaved typing (see coolify#6062 / #6354 / #9695).
$this->server->refresh();
- $this->syncData();
-
- // Update validation state
+ $this->server->settings->refresh();
$this->isValidating = $this->server->is_validating ?? false;
+ $this->validationLogs = $this->server->validation_logs;
+ $this->isReachable = $this->server->settings->is_reachable;
+ $this->isUsable = $this->server->settings->is_usable;
// Reload Hetzner tokens in case the linking section should now be shown
$this->loadHetznerTokens();
diff --git a/resources/views/livewire/project/database/redis/general.blade.php b/resources/views/livewire/project/database/redis/general.blade.php
index 73ee5f0e5..b72b05ff6 100644
--- a/resources/views/livewire/project/database/redis/general.blade.php
+++ b/resources/views/livewire/project/database/redis/general.blade.php
@@ -60,56 +60,8 @@
helper="A comma separated list of ports you would like to map to the host system.
Example3000:5432,3002:5433"
canGate="update" :canResource="$database" />
-