feat(server): paginate Docker images with adjustable page size

Reset pagination when search or page size changes and clamp page bounds.
Move disk usage badges beside the Images heading.
This commit is contained in:
Andras Bacsai
2026-10-13 10:02:19 +02:00
parent 3863f0d257
commit dc6777a71d
4 changed files with 74 additions and 5 deletions
+29 -1
View File
@@ -4,12 +4,15 @@ namespace App\Livewire\Server;
use App\Models\Server;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Pagination\LengthAwarePaginator;
use Livewire\Attributes\Locked;
use Livewire\Component;
use Livewire\WithPagination;
class DockerImages extends Component
{
use AuthorizesRequests;
use WithPagination;
public Server $server;
@@ -27,6 +30,19 @@ class DockerImages extends Component
public bool $loaded = false;
public int $perPage = 10;
public function updatedSearch(): void
{
$this->resetPage();
}
public function updatedPerPage(): void
{
$this->perPage = max(1, min(100, $this->perPage));
$this->resetPage();
}
public function mount(string $server_uuid)
{
try {
@@ -159,10 +175,22 @@ class DockerImages extends Component
->contains($search, ignoreCase: true));
}
$this->perPage = max(1, min(100, $this->perPage));
$lastPage = max(1, (int) ceil($images->count() / $this->perPage));
$page = max(1, min((int) $this->getPage(), $lastPage));
if ($page !== $this->getPage()) {
$this->setPage($page);
}
return view('livewire.server.docker-images', [
// Not "images": Livewire shares public properties with the view, so that name is
// already taken by the unfiltered array.
'visibleImages' => $images->values(),
'visibleImages' => new LengthAwarePaginator(
$images->forPage($page, $this->perPage)->values(),
$images->count(),
$this->perPage,
$page,
),
]);
}
}
@@ -7,7 +7,7 @@
<section {{ $attributes->merge(['class' => 'application-settings-section', 'data-settings-section-title' => $title]) }}>
<header>
<div class="min-w-0 py-0.5">
<div @class(['min-w-0 py-0.5', 'flex flex-wrap items-center gap-2' => isset($badges)])>
@if (filled($description ?? $helper))
<h3>
<x-helper :helper="$description ?? $helper" :label="'More information about '.$title">
@@ -19,6 +19,7 @@
@else
<h3>{{ $title }}</h3>
@endif
{{ $badges ?? '' }}
</div>
@isset($actions)
<div class="flex min-w-0 max-w-full flex-wrap items-center gap-2">
@@ -13,11 +13,13 @@
<x-application.settings-section id="server-docker-images-section" title="Images"
helper="Docker images stored on this server. Images that no container uses can be deleted here."
flush>
<x-slot:actions>
@if ($usage)
@if ($usage)
<x-slot:badges>
<x-status-badge :status="'Total ' . $usage['size']" type="neutral" />
<x-status-badge :status="'Reclaimable ' . $usage['reclaimable']" type="warning" />
@endif
</x-slot:badges>
@endif
<x-slot:actions>
<x-forms.button wire:click="load">
<x-reicon name="refresh" class="size-3.5" />
Refresh
@@ -104,6 +106,15 @@
</div>
@endforeach
</div>
<x-table-pagination :from="$visibleImages->firstItem()" :to="$visibleImages->lastItem()"
:total="$visibleImages->total()" :current-page="$visibleImages->currentPage()"
:last-page="$visibleImages->lastPage()" wire-target="previousPage,nextPage,perPage"
previous-action="previousPage" next-action="nextPage">
<x-slot:pageSize>
<x-page-size-select model="perPage" livewire
storage-key="coolify.page-size.server-docker-images" />
</x-slot:pageSize>
</x-table-pagination>
@endif
</x-application.settings-section>
</div>
+29
View File
@@ -101,3 +101,32 @@ it('filters images by the search term', function () use ($fakeDocker, $nginxId,
->assertSee('1.27')
->assertDontSee(str_repeat('b', 12));
});
it('paginates images and returns to the first page when the search changes', function () {
Process::fake([
'*docker image ls*' => Process::result(output: collect(range(1, 12))
->map(fn (int $i) => json_encode([
'Repository' => sprintf('app-%02d', $i), 'Tag' => 'latest', 'ID' => 'sha256:'.str_repeat(dechex($i), 64),
'Size' => '1MB', 'CreatedSince' => '1 day ago',
]))
->implode("\n")),
'*docker ps -a*' => Process::result(output: ''),
'*docker system df*' => Process::result(output: ''),
]);
Livewire::test(DockerImages::class, ['server_uuid' => $this->server->uuid])
->call('load')
->assertSee('app-10')
->assertDontSee('app-11')
->assertSee('1–10 of 12')
->call('nextPage')
->assertSee('app-12')
->assertDontSee('app-01')
->assertSee('11–12 of 12')
->set('search', 'app-0')
->assertSee('app-01')
->assertSee('1–9 of 9')
->set('perPage', 5)
->assertSee('1–5 of 9')
->assertDontSee('app-06');
});