fix(livewire): scope DatabaseProxyStopped to proxy fields, harden status trait

Clickhouse, Dragonfly, and Keydb still called syncData() inside the
DatabaseProxyStopped broadcast handler, clobbering in-progress edits to
name/description/credentials. Refresh only is_public/public_port/
public_port_timeout instead, matching the pattern used elsewhere.

Also null-guard HasDatabaseStatusInfo::getListeners() against an absent
Auth::user()/currentTeam(), add explicit return types on getListeners()
and render(), and convert inline comments in the SSL refresh test to a
PHPDoc block.
This commit is contained in:
Aditya Tripathi 2026-05-21 10:24:49 +00:00
parent e7e65831a7
commit 7a3fcd37d5
5 changed files with 37 additions and 18 deletions

View file

@ -192,9 +192,12 @@ public function instantSave()
} }
} }
public function databaseProxyStopped() public function databaseProxyStopped(): void
{ {
$this->syncData(); $this->database->refresh();
$this->isPublic = $this->database->is_public;
$this->publicPort = $this->database->public_port;
$this->publicPortTimeout = $this->database->public_port_timeout;
$this->dispatch('databaseUpdated'); $this->dispatch('databaseUpdated');
} }

View file

@ -184,9 +184,12 @@ public function instantSave()
} }
} }
public function databaseProxyStopped() public function databaseProxyStopped(): void
{ {
$this->syncData(); $this->database->refresh();
$this->isPublic = $this->database->is_public;
$this->publicPort = $this->database->public_port;
$this->publicPortTimeout = $this->database->public_port_timeout;
$this->dispatch('databaseUpdated'); $this->dispatch('databaseUpdated');
} }

View file

@ -189,9 +189,12 @@ public function instantSave()
} }
} }
public function databaseProxyStopped() public function databaseProxyStopped(): void
{ {
$this->syncData(); $this->database->refresh();
$this->isPublic = $this->database->is_public;
$this->publicPort = $this->database->public_port;
$this->publicPortTimeout = $this->database->public_port_timeout;
$this->dispatch('databaseUpdated'); $this->dispatch('databaseUpdated');
} }

View file

@ -5,6 +5,7 @@
use App\Helpers\SslHelper; use App\Helpers\SslHelper;
use Carbon\Carbon; use Carbon\Carbon;
use Exception; use Exception;
use Illuminate\Contracts\View\View;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
/** /**
@ -51,16 +52,23 @@ protected function showPublicUrlPlaceholder(): bool
return false; return false;
} }
public function getListeners() public function getListeners(): array
{ {
$userId = Auth::id(); $listeners = ['databaseUpdated' => 'refresh'];
$teamId = Auth::user()->currentTeam()->id;
return [ $user = Auth::user();
"echo-private:user.{$userId},DatabaseStatusChanged" => 'refresh', if (! $user) {
"echo-private:team.{$teamId},ServiceChecked" => 'refresh', return $listeners;
'databaseUpdated' => 'refresh', }
];
$listeners["echo-private:user.{$user->id},DatabaseStatusChanged"] = 'refresh';
$team = $user->currentTeam();
if ($team) {
$listeners["echo-private:team.{$team->id},ServiceChecked"] = 'refresh';
}
return $listeners;
} }
public function mount(): void public function mount(): void
@ -150,7 +158,7 @@ public function regenerateSslCertificate(): void
} }
} }
public function render() public function render(): View
{ {
return view('livewire.project.database.status-info', [ return view('livewire.project.database.status-info', [
'label' => $this->databaseLabel(), 'label' => $this->databaseLabel(),

View file

@ -78,11 +78,13 @@
->not->toHaveKey("echo-private:team.{$this->team->id},ServiceStatusChanged"); ->not->toHaveKey("echo-private:team.{$this->team->id},ServiceStatusChanged");
})->with('database-general-forms-without-broadcasts'); })->with('database-general-forms-without-broadcasts');
/**
* Resolve a Livewire component's listeners regardless of whether the subclass
* exposes getListeners() publicly or only declares a $listeners array the
* HandlesEvents trait keeps getListeners() protected by default.
*/
function resolveLivewireListeners(object $component): array 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 = new ReflectionMethod($component, 'getListeners');
$method->setAccessible(true); $method->setAccessible(true);