fix(livewire): stop broadcast handlers from wiping in-progress form input
This commit is contained in:
parent
65c0c92c02
commit
b9f773c1d9
18 changed files with 470 additions and 247 deletions
|
|
@ -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]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 [
|
||||
|
|
|
|||
|
|
@ -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 [
|
||||
|
|
|
|||
|
|
@ -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 [
|
||||
|
|
|
|||
|
|
@ -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 [
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
116
app/Livewire/Project/Database/Redis/StatusInfo.php
Normal file
116
app/Livewire/Project/Database/Redis/StatusInfo.php
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
<?php
|
||||
|
||||
namespace App\Livewire\Project\Database\Redis;
|
||||
|
||||
use App\Helpers\SslHelper;
|
||||
use App\Models\StandaloneRedis;
|
||||
use Carbon\Carbon;
|
||||
use Exception;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Livewire\Component;
|
||||
|
||||
class StatusInfo extends Component
|
||||
{
|
||||
use AuthorizesRequests;
|
||||
|
||||
public StandaloneRedis $database;
|
||||
|
||||
public bool $enableSsl = false;
|
||||
|
||||
public ?Carbon $certificateValidUntil = null;
|
||||
|
||||
public ?string $dbUrl = null;
|
||||
|
||||
public ?string $dbUrlPublic = 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',
|
||||
'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');
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -60,56 +60,8 @@
|
|||
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="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="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 ($enableSsl && $certificateValidUntil)
|
||||
<x-modal-confirmation title="Regenerate SSL Certificates"
|
||||
buttonTitle="Regenerate SSL Certificates" :actions="[
|
||||
'The SSL certificate of this database will be regenerated.',
|
||||
'You must restart the database after regenerating the certificate to start using the new certificate.',
|
||||
]"
|
||||
submitAction="regenerateSslCertificate" :confirmWithText="false" :confirmWithPassword="false" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@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>
|
||||
@elseif(now()->addDays(30)->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expiring
|
||||
soon</span>
|
||||
@else
|
||||
<span>{{ $certificateValidUntil->format('d.m.Y H:i:s') }}</span>
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
<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="enableSsl" label="Enable SSL"
|
||||
wire:model.live="enableSsl" instantSave="instantSaveSSL" canGate="update"
|
||||
:canResource="$database" />
|
||||
@else
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
<livewire:project.database.redis.status-info :database="$database" />
|
||||
<div>
|
||||
<div class="flex flex-col py-2 w-64">
|
||||
<div class="flex items-center gap-2 pb-2">
|
||||
|
|
|
|||
|
|
@ -0,0 +1,51 @@
|
|||
<div class="flex flex-col gap-2">
|
||||
<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="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="dbUrlPublic" canGate="update" :canResource="$database" />
|
||||
@endif
|
||||
<div class="flex flex-col gap-2 pt-4">
|
||||
<div class="flex items-center justify-between py-2">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<h3>SSL Configuration</h3>
|
||||
@if ($enableSsl && $certificateValidUntil)
|
||||
<x-modal-confirmation title="Regenerate SSL Certificates"
|
||||
buttonTitle="Regenerate SSL Certificates" :actions="[
|
||||
'The SSL certificate of this database will be regenerated.',
|
||||
'You must restart the database after regenerating the certificate to start using the new certificate.',
|
||||
]"
|
||||
submitAction="regenerateSslCertificate" :confirmWithText="false" :confirmWithPassword="false" />
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@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>
|
||||
@elseif(now()->addDays(30)->gt($certificateValidUntil))
|
||||
<span class="text-red-500">{{ $certificateValidUntil->format('d.m.Y H:i:s') }} - Expiring
|
||||
soon</span>
|
||||
@else
|
||||
<span>{{ $certificateValidUntil->format('d.m.Y H:i:s') }}</span>
|
||||
@endif
|
||||
</span>
|
||||
@endif
|
||||
<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="enableSsl" label="Enable SSL"
|
||||
wire:model.live="enableSsl" instantSave="instantSaveSSL" canGate="update"
|
||||
:canResource="$database" />
|
||||
@else
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -7,11 +7,16 @@
|
|||
use App\Livewire\Project\Database\Mysql\General as MysqlGeneral;
|
||||
use App\Livewire\Project\Database\Postgresql\General as PostgresqlGeneral;
|
||||
use App\Livewire\Project\Database\Redis\General as RedisGeneral;
|
||||
use App\Livewire\Project\Database\Redis\StatusInfo as RedisStatusInfo;
|
||||
use App\Livewire\Server\Sentinel;
|
||||
use App\Livewire\Server\Show;
|
||||
use App\Models\Environment;
|
||||
use App\Models\Project;
|
||||
use App\Models\Server;
|
||||
use App\Models\StandaloneDocker;
|
||||
use App\Models\StandaloneMysql;
|
||||
use App\Models\StandalonePostgresql;
|
||||
use App\Models\StandaloneRedis;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
|
|
@ -28,25 +33,75 @@
|
|||
session(['currentTeam' => $this->team]);
|
||||
});
|
||||
|
||||
dataset('ssl-aware-database-general-components', [
|
||||
dataset('database-general-forms-without-broadcasts', [
|
||||
// Redis splits status-derived display into a sibling component; the form itself
|
||||
// takes no broadcast listeners. Other DBs use the narrower refreshStatus pattern below.
|
||||
RedisGeneral::class,
|
||||
]);
|
||||
|
||||
dataset('database-general-forms-with-narrow-refresh', [
|
||||
// Form listens to status broadcasts but routes them to refreshStatus, which only
|
||||
// writes display-only properties (URLs, cert expiry) — never input-bound text fields.
|
||||
PostgresqlGeneral::class,
|
||||
MysqlGeneral::class,
|
||||
MariadbGeneral::class,
|
||||
MongodbGeneral::class,
|
||||
RedisGeneral::class,
|
||||
PostgresqlGeneral::class,
|
||||
KeydbGeneral::class,
|
||||
DragonflyGeneral::class,
|
||||
]);
|
||||
|
||||
it('maps database status broadcasts to refresh for ssl-aware database general components', function (string $componentClass) {
|
||||
$component = app($componentClass);
|
||||
$listeners = $component->getListeners();
|
||||
dataset('database-status-info-components', [
|
||||
RedisStatusInfo::class,
|
||||
]);
|
||||
|
||||
expect($listeners["echo-private:user.{$this->user->id},DatabaseStatusChanged"])->toBe('refresh')
|
||||
->and($listeners["echo-private:team.{$this->team->id},ServiceChecked"])->toBe('refresh');
|
||||
})->with('ssl-aware-database-general-components');
|
||||
it('does not subscribe the form to status broadcasts when display lives in a sibling', function (string $componentClass) {
|
||||
// Regression guard for coolify#6062 / #6354 / #9695:
|
||||
// For DBs whose status-derived display moved into a sibling component, the form
|
||||
// itself must not subscribe to status broadcasts at all.
|
||||
$listeners = resolveLivewireListeners(app($componentClass));
|
||||
|
||||
it('reloads the mysql database model when refreshing so ssl controls follow the latest status', function () {
|
||||
expect($listeners)
|
||||
->not->toHaveKey("echo-private:user.{$this->user->id},DatabaseStatusChanged")
|
||||
->not->toHaveKey("echo-private:team.{$this->team->id},ServiceChecked")
|
||||
->not->toHaveKey("echo-private:team.{$this->team->id},ServiceStatusChanged");
|
||||
})->with('database-general-forms-without-broadcasts');
|
||||
|
||||
it('routes status broadcasts to refreshStatus, never to a handler that re-syncs inputs', function (string $componentClass) {
|
||||
// Regression guard for coolify#6062 / #6354 / #9695:
|
||||
// The form may listen to broadcasts, but only to a narrow handler (refreshStatus)
|
||||
// that touches display-only properties. Routing to `refresh` or `$refresh` would
|
||||
// re-sync every input property from the DB and wipe in-progress typing.
|
||||
$listeners = resolveLivewireListeners(app($componentClass));
|
||||
|
||||
$databaseStatusKey = "echo-private:user.{$this->user->id},DatabaseStatusChanged";
|
||||
$serviceCheckedKey = "echo-private:team.{$this->team->id},ServiceChecked";
|
||||
|
||||
expect($listeners[$databaseStatusKey] ?? null)->toBe('refreshStatus')
|
||||
->and($listeners[$serviceCheckedKey] ?? null)->toBe('refreshStatus');
|
||||
})->with('database-general-forms-with-narrow-refresh');
|
||||
|
||||
function resolveLivewireListeners(object $component): array
|
||||
{
|
||||
// Livewire's HandlesEvents trait declares getListeners() as protected,
|
||||
// so subclasses that override it as public are callable directly, but
|
||||
// subclasses that rely on $listeners are not. Reflection handles both.
|
||||
$method = new ReflectionMethod($component, 'getListeners');
|
||||
$method->setAccessible(true);
|
||||
|
||||
return (array) $method->invoke($component);
|
||||
}
|
||||
|
||||
it('auto-refreshes status-info sibling on database status broadcasts', function (string $componentClass) {
|
||||
// Status-derived display (connection URLs, SSL gate hint, cert expiry) lives in a sibling
|
||||
// Livewire component so it can re-render on broadcasts without touching the form's DOM.
|
||||
$listeners = resolveLivewireListeners(app($componentClass));
|
||||
|
||||
expect($listeners)
|
||||
->toHaveKey("echo-private:user.{$this->user->id},DatabaseStatusChanged")
|
||||
->toHaveKey("echo-private:team.{$this->team->id},ServiceChecked");
|
||||
})->with('database-status-info-components');
|
||||
|
||||
it('reloads the mysql database model when refresh is called directly so ssl controls follow the latest status', function () {
|
||||
$server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||
$destination = StandaloneDocker::where('server_id', $server->id)->first();
|
||||
$project = Project::factory()->create(['team_id' => $this->team->id]);
|
||||
|
|
@ -75,3 +130,91 @@
|
|||
$component->call('refresh')
|
||||
->assertSee('Database should be stopped to change this settings.');
|
||||
});
|
||||
|
||||
it('does not clobber server form text inputs when sentinel restarts', function () {
|
||||
$server = Server::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'name' => 'persisted-server-name',
|
||||
]);
|
||||
|
||||
$component = Livewire::test(Sentinel::class, ['server_uuid' => $server->uuid])
|
||||
->set('sentinelToken', 'user-was-typing-this-token');
|
||||
|
||||
$component->call('handleSentinelRestarted', ['serverUuid' => $server->uuid]);
|
||||
|
||||
expect($component->get('sentinelToken'))->toBe('user-was-typing-this-token');
|
||||
});
|
||||
|
||||
it('does not clobber server form text inputs when server validation completes', function () {
|
||||
$server = Server::factory()->create([
|
||||
'team_id' => $this->team->id,
|
||||
'name' => 'persisted-server-name',
|
||||
]);
|
||||
|
||||
$component = Livewire::test(Show::class, ['server_uuid' => $server->uuid])
|
||||
->set('name', 'user-was-typing-here')
|
||||
->set('ip', '203.0.113.42');
|
||||
|
||||
$component->call('handleServerValidated', ['serverUuid' => $server->uuid]);
|
||||
|
||||
expect($component->get('name'))->toBe('user-was-typing-here')
|
||||
->and($component->get('ip'))->toBe('203.0.113.42');
|
||||
});
|
||||
|
||||
it('preserves typed input on the postgres form when refreshStatus runs', function () {
|
||||
$server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||
$destination = StandaloneDocker::where('server_id', $server->id)->first();
|
||||
$project = Project::factory()->create(['team_id' => $this->team->id]);
|
||||
$environment = Environment::factory()->create(['project_id' => $project->id]);
|
||||
|
||||
$database = StandalonePostgresql::create([
|
||||
'name' => 'persisted-name',
|
||||
'image' => 'postgres:16',
|
||||
'postgres_user' => 'postgres',
|
||||
'postgres_password' => 'password',
|
||||
'postgres_db' => 'postgres',
|
||||
'status' => 'exited:unhealthy',
|
||||
'enable_ssl' => false,
|
||||
'is_log_drain_enabled' => false,
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
]);
|
||||
|
||||
$component = Livewire::test(PostgresqlGeneral::class, ['database' => $database])
|
||||
->set('name', 'user-was-typing-here')
|
||||
->set('portsMappings', '5433:5432');
|
||||
|
||||
$component->call('refreshStatus');
|
||||
|
||||
expect($component->get('name'))->toBe('user-was-typing-here')
|
||||
->and($component->get('portsMappings'))->toBe('5433:5432');
|
||||
});
|
||||
|
||||
it('shows the redis ssl gate hint after the sibling is refreshed', function () {
|
||||
$server = Server::factory()->create(['team_id' => $this->team->id]);
|
||||
$destination = StandaloneDocker::where('server_id', $server->id)->first();
|
||||
$project = Project::factory()->create(['team_id' => $this->team->id]);
|
||||
$environment = Environment::factory()->create(['project_id' => $project->id]);
|
||||
|
||||
$database = StandaloneRedis::create([
|
||||
'name' => 'test-redis',
|
||||
'image' => 'redis:7',
|
||||
'redis_password' => 'password',
|
||||
'redis_username' => 'default',
|
||||
'status' => 'exited:unhealthy',
|
||||
'enable_ssl' => true,
|
||||
'is_log_drain_enabled' => false,
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination->getMorphClass(),
|
||||
]);
|
||||
|
||||
$component = Livewire::test(RedisStatusInfo::class, ['database' => $database])
|
||||
->assertDontSee('Database should be stopped to change this settings.');
|
||||
|
||||
$database->fill(['status' => 'running:healthy'])->save();
|
||||
|
||||
$component->call('refresh')
|
||||
->assertSee('Database should be stopped to change this settings.');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue