diff --git a/app/Livewire/Project/Database/FlushCache.php b/app/Livewire/Project/Database/FlushCache.php new file mode 100644 index 0000000000..96bbe0b9c4 --- /dev/null +++ b/app/Livewire/Project/Database/FlushCache.php @@ -0,0 +1,61 @@ +authorize('view', $this->database); + } + + public function flush(): void + { + try { + $this->authorize('manage', $this->database); + + if (! $this->isCacheDatabase()) { + throw new \RuntimeException('This database type does not support cache flushing.'); + } + + FlushCacheDatabase::run($this->database); + $this->auditFlush(); + $this->dispatch('success', 'Cache flushed successfully.'); + } catch (\Throwable $e) { + handleError($e, $this); + } + } + + public function isCacheDatabase(): bool + { + return $this->database instanceof StandaloneRedis + || $this->database instanceof StandaloneKeydb + || $this->database instanceof StandaloneDragonfly; + } + + private function auditFlush(): void + { + auditLog('ui.database.flushed', [ + 'team_id' => $this->database->team()?->id, + 'database_uuid' => $this->database->uuid, + 'database_name' => $this->database->name, + ]); + } + + public function render(): View + { + return view('livewire.project.database.flush-cache'); + } +} diff --git a/app/Livewire/Project/Database/Heading.php b/app/Livewire/Project/Database/Heading.php index 354d9ed8d2..a7e8cfedd8 100644 --- a/app/Livewire/Project/Database/Heading.php +++ b/app/Livewire/Project/Database/Heading.php @@ -2,15 +2,11 @@ namespace App\Livewire\Project\Database; -use App\Actions\Database\FlushCacheDatabase; use App\Actions\Database\RestartDatabase; use App\Actions\Database\StartDatabase; use App\Actions\Database\StopDatabase; use App\Actions\Docker\GetContainersStatus; use App\Events\ServiceStatusChanged; -use App\Models\StandaloneDragonfly; -use App\Models\StandaloneKeydb; -use App\Models\StandaloneRedis; use App\Support\ResourceStartActivity; use App\Traits\ListensToTeamChannel; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; @@ -150,23 +146,6 @@ class Heading extends Component } } - public function flush() - { - try { - $this->authorize('manage', $this->database); - - if (! $this->isCacheDatabase()) { - throw new \RuntimeException('This database type does not support cache flushing.'); - } - - FlushCacheDatabase::run($this->database); - $this->auditDatabaseAction('ui.database.flushed'); - $this->dispatch('success', 'Cache flushed successfully.'); - } catch (\Throwable $e) { - return handleError($e, $this); - } - } - public function restart() { try { @@ -213,17 +192,9 @@ class Heading extends Component 'checkboxes' => [ ['id' => 'docker_cleanup', 'label' => __('resource.docker_cleanup')], ], - 'isCacheDatabase' => $this->isCacheDatabase(), ]); } - private function isCacheDatabase(): bool - { - return $this->database instanceof StandaloneRedis - || $this->database instanceof StandaloneKeydb - || $this->database instanceof StandaloneDragonfly; - } - private function auditDatabaseAction(string $event): void { auditLog($event, [ diff --git a/resources/views/livewire/project/database/configuration.blade.php b/resources/views/livewire/project/database/configuration.blade.php index 23778effa3..f7c290bd5d 100644 --- a/resources/views/livewire/project/database/configuration.blade.php +++ b/resources/views/livewire/project/database/configuration.blade.php @@ -53,6 +53,9 @@ @elseif ($currentRoute === 'project.database.tags') @elseif ($currentRoute === 'project.database.danger') + @if (in_array($database->type(), ['standalone-redis', 'standalone-keydb', 'standalone-dragonfly'])) + + @endif @endif diff --git a/resources/views/livewire/project/database/flush-cache.blade.php b/resources/views/livewire/project/database/flush-cache.blade.php new file mode 100644 index 0000000000..9de2ee5666 --- /dev/null +++ b/resources/views/livewire/project/database/flush-cache.blade.php @@ -0,0 +1,43 @@ +
+ +
+
+
+
+

Flush cache

+ +
+

+ Permanently erase every key in + {{ $database->name }} + by running FLUSHALL ASYNC inside the running container. +

+
    +
  • • All cached data is removed and cannot be recovered.
  • +
  • • The database container keeps running; only its data is cleared.
  • +
  • • The database must be running for this action to work.
  • +
+
+ +
+ @can('manage', $database) + + @else + + Flush cache + + @endcan +
+
+
+
+
diff --git a/resources/views/livewire/project/database/heading.blade.php b/resources/views/livewire/project/database/heading.blade.php index 43e6b5af4b..abf58177c7 100644 --- a/resources/views/livewire/project/database/heading.blade.php +++ b/resources/views/livewire/project/database/heading.blade.php @@ -88,13 +88,6 @@ Restart - @if ($isCacheDatabase) - - @endif - @endif - @if ($isCacheDatabase) - - - - - - @endif @endif diff --git a/tests/Feature/DatabaseFlushCacheTest.php b/tests/Feature/DatabaseFlushCacheTest.php index a0a053a067..8c3e68d943 100644 --- a/tests/Feature/DatabaseFlushCacheTest.php +++ b/tests/Feature/DatabaseFlushCacheTest.php @@ -1,7 +1,7 @@ 'app-postgres', + 'image' => 'postgres:16', + 'postgres_user' => 'coolify', + 'postgres_password' => 'password', + 'postgres_db' => 'coolify', + 'status' => 'running:healthy', + 'environment_id' => $environment->id, + 'destination_id' => $destination->id, + 'destination_type' => $destination->getMorphClass(), + ]); +} + test('flushes the cache of a running redis database and records an audit event', function () { $redis = makeRedis($this->environment, $this->destination); FlushCacheDatabase::mock()->shouldReceive('handle')->once(); - Livewire::test(Heading::class, ['database' => $redis]) + Livewire::test(FlushCache::class, ['database' => $redis]) ->call('flush') ->assertDispatched('success'); @@ -65,21 +80,11 @@ test('flushes the cache of a running redis database and records an audit event', }); test('refuses to flush a non-cache database and does not run the action', function () { - $postgres = StandalonePostgresql::create([ - 'name' => 'app-postgres', - 'image' => 'postgres:16', - 'postgres_user' => 'coolify', - 'postgres_password' => 'password', - 'postgres_db' => 'coolify', - 'status' => 'running:healthy', - 'environment_id' => $this->environment->id, - 'destination_id' => $this->destination->id, - 'destination_type' => $this->destination->getMorphClass(), - ]); + $postgres = makePostgres($this->environment, $this->destination); FlushCacheDatabase::mock()->shouldReceive('handle')->never(); - Livewire::test(Heading::class, ['database' => $postgres]) + Livewire::test(FlushCache::class, ['database' => $postgres]) ->call('flush') ->assertDispatched('error'); @@ -96,31 +101,30 @@ test('denies flushing to a member without manage permission', function () { FlushCacheDatabase::mock()->shouldReceive('handle')->never(); - Livewire::test(Heading::class, ['database' => $redis]) + Livewire::test(FlushCache::class, ['database' => $redis]) ->call('flush') ->assertDispatched('error'); expect(AuditEvent::query()->where('event', 'ui.database.flushed')->exists())->toBeFalse(); }); -test('only cache databases expose the flush cache action in the heading', function () { +test('renders the flush cache card for a cache database', function () { $redis = makeRedis($this->environment, $this->destination); - Livewire::test(Heading::class, ['database' => $redis]) - ->assertSee('Flush cache'); + Livewire::test(FlushCache::class, ['database' => $redis]) + ->assertSee('Flush cache') + ->assertSee('FLUSHALL ASYNC'); +}); - $postgres = StandalonePostgresql::create([ - 'name' => 'app-postgres', - 'image' => 'postgres:16', - 'postgres_user' => 'coolify', - 'postgres_password' => 'password', - 'postgres_db' => 'coolify', - 'status' => 'running:healthy', - 'environment_id' => $this->environment->id, - 'destination_id' => $this->destination->id, - 'destination_type' => $this->destination->getMorphClass(), - ]); +test('the danger page wires the flush cache section only for cache database types', function () { + $source = file_get_contents(resource_path('views/livewire/project/database/configuration.blade.php')); - Livewire::test(Heading::class, ['database' => $postgres]) - ->assertDontSee('Flush cache'); + // The flush-cache component is rendered on the danger route, guarded to the three cache types. + expect($source) + ->toContain('project.database.flush-cache') + ->toMatch('/in_array\(\$database->type\(\), \[.*standalone-redis.*standalone-keydb.*standalone-dragonfly.*\]\)[\s\S]*project\.database\.flush-cache/'); + + // It must no longer live in the heading action menu. + $heading = file_get_contents(resource_path('views/livewire/project/database/heading.blade.php')); + expect($heading)->not->toContain('Flush cache'); });