diff --git a/app/Livewire/Images/Images/Index.php b/app/Livewire/Images/Images/Index.php index a04e28ec1..79fbc4458 100644 --- a/app/Livewire/Images/Images/Index.php +++ b/app/Livewire/Images/Images/Index.php @@ -2,66 +2,161 @@ namespace App\Livewire\Images\Images; +use App\Actions\Docker\DeleteAllDanglingServerDockerImages; +use App\Actions\Docker\GetServerDockerImageDetails; +use App\Actions\Docker\ListServerDockerImages; use App\Models\Server; use Illuminate\Database\Eloquent\Collection; +use Illuminate\Support\Collection as SupportCollection; +use Illuminate\Support\Facades\Auth; use Livewire\Component; class Index extends Component { public string $selected_uuid = 'default'; - public array $serverImages = []; + public SupportCollection $serverImages; public Collection $servers; public bool $isLoadingImages = false; + public array $selectedImages = []; + public ?array $imageDetails = null; + public string $searchQuery = ''; + public bool $showOnlyDangling = false; + public bool $selectAll = false; public function mount() { - if (!auth()->user()->isAdmin()) { - abort(403); - } $this->servers = Server::isReachable()->get(); + $this->serverImages = collect([]); } - //Zove se automatski kad se na fronti selecta server public function updatedSelectedUuid() { $this->loadServerImages(); + $this->selectedImages = []; } public function loadServerImages() { $this->isLoadingImages = true; + $this->imageDetails = null; try { if ($this->selected_uuid === 'default') { - dd('Please select a server.'); return; } $server = $this->servers->firstWhere('uuid', $this->selected_uuid); if (!$server) { - dd('Server not found'); return; } - // 1. Koristi instant_remote_process "docker images" - // 2. Parse output u $serverImages array - // 3. repository, tag, id, size, created_at - + $this->serverImages = collect(ListServerDockerImages::run($server)); } catch (\Exception $e) { - dd("Error loading docker images: " . $e->getMessage()); + $this->addError('images', "Error loading docker images: " . $e->getMessage()); } finally { $this->isLoadingImages = false; } } - public function getImageDetails($imageId) {} - public function deleteImage($imageId) {} + public function getImageDetails($imageId) + { + try { + $server = $this->servers->firstWhere('uuid', $this->selected_uuid); + if (!$server) { + return; + } + $this->imageDetails = GetServerDockerImageDetails::run($server, $imageId); - public function pruneUnused() {} + // Add formatted size + if (isset($this->imageDetails[0]['Size'])) { + $size = $this->imageDetails[0]['Size']; + $this->imageDetails[0]['FormattedSize'] = $this->formatBytes($size); + } + // Add formatted creation date + if (isset($this->imageDetails[0]['Created'])) { + $this->imageDetails[0]['FormattedCreated'] = \Carbon\Carbon::parse($this->imageDetails[0]['Created'])->diffForHumans(); + } + } catch (\Exception $e) { + $this->addError('details', "Error loading image details: " . $e->getMessage()); + } + } + + public function deleteImage($imageId) + { + try { + $server = $this->servers->firstWhere('uuid', $this->selected_uuid); + if (!$server) { + return; + } + + instant_remote_process(["docker rmi -f {$imageId}"], $server); + $this->imageDetails = null; + $this->loadServerImages(); + } catch (\Exception $e) { + $this->addError('delete', "Error deleting image: " . $e->getMessage()); + } + } + + public function pruneUnused() + { + try { + $server = $this->servers->firstWhere('uuid', $this->selected_uuid); + if (!$server) { + return; + } + + DeleteAllDanglingServerDockerImages::run($server); + $this->loadServerImages(); + } catch (\Exception $e) { + $this->addError('prune', "Error pruning images: " . $e->getMessage()); + } + } + + public function getFilteredImagesProperty() + { + return $this->serverImages + ->when($this->searchQuery, function ($collection) { + return $collection->filter(function ($image) { + return str_contains(strtolower($image['Repository'] ?? ''), strtolower($this->searchQuery)) || + str_contains(strtolower($image['Tag'] ?? ''), strtolower($this->searchQuery)) || + str_contains(strtolower($image['ID'] ?? ''), strtolower($this->searchQuery)); + }); + }) + ->when($this->showOnlyDangling, function ($collection) { + return $collection->filter(function ($image) { + return ($image['Repository'] ?? '') === '' || ($image['Tag'] ?? '') === ''; + }); + }) + ->values(); + } + + public function updatedSelectAll($value) + { + if ($value) { + $this->selectedImages = $this->filteredImages->pluck('ID')->toArray(); + } else { + $this->selectedImages = []; + } + } + + protected function formatBytes($bytes, $precision = 2) + { + $units = ['B', 'KB', 'MB', 'GB', 'TB']; + + $bytes = max($bytes, 0); + $pow = floor(($bytes ? log($bytes) : 0) / log(1024)); + $pow = min($pow, count($units) - 1); + + $bytes /= pow(1024, $pow); + + return round($bytes, $precision) . ' ' . $units[$pow]; + } public function render() { - return view('livewire.images.images.index'); + return view('livewire.images.images.index', [ + 'filteredImages' => $this->getFilteredImagesProperty() + ]); } } diff --git a/resources/views/livewire/images/images/index.blade.php b/resources/views/livewire/images/images/index.blade.php index 4fc5fef47..1b2fcee0e 100644 --- a/resources/views/livewire/images/images/index.blade.php +++ b/resources/views/livewire/images/images/index.blade.php @@ -7,10 +7,8 @@

Docker Images

+ @foreach ($servers as $server) - @if ($loop->first) - - @endif @endforeach @@ -20,8 +18,35 @@
+ + @if ($selected_uuid !== 'default') +
+ + Prune Unused + + + Delete Selected ({{ count($selectedImages) }}) + +
+ @endif + @if ($selected_uuid !== 'default') +
+
+ +
+ +
+ @endif +
@@ -33,6 +58,9 @@ + @@ -42,25 +70,46 @@ + - {{-- TODO: Implement image listing logic --}} - {{-- Example row structure: - - - - - - - --}} + @forelse ($filteredImages as $image) + + + + + + + + + @empty + + + + @endforelse
+ + Repository Image ID + Size Actions
repository_nametagimage_id -
- Details -
-
+ + {{ $image['Repository'] }}{{ $image['Tag'] }} + {{ substr($image['ID'], 7, 12) }}{{ $image['Size'] }} +
+ + Details + + + Delete + +
+
+ No images found +
@@ -71,5 +120,99 @@ @endif
+ + @if ($imageDetails) +
+
+
+

Image Details

+ +
+ +
+
+
+

ID:

+

{{ $imageDetails[0]['Id'] ?? 'N/A' }}

+
+
+

Created:

+

{{ $imageDetails[0]['FormattedCreated'] ?? 'N/A' }}

+
+
+

Size:

+

{{ $imageDetails[0]['FormattedSize'] ?? 'N/A' }}

+
+
+

Container Count:

+

{{ $imageDetails[0]['ContainerCount'] ?? 'N/A' }}

+
+
+ + @if (isset($imageDetails[0]['Config'])) +
+

Configuration

+
+ @if (isset($imageDetails[0]['Config']['Env']) && !empty($imageDetails[0]['Config']['Env'])) +
+
Environment Variables
+
+ @foreach ($imageDetails[0]['Config']['Env'] as $env) +
{{ $env }}
+ @endforeach +
+
+ @endif + + @if (isset($imageDetails[0]['Config']['ExposedPorts']) && !empty($imageDetails[0]['Config']['ExposedPorts'])) +
+
Exposed Ports
+
+ @foreach (array_keys($imageDetails[0]['Config']['ExposedPorts']) as $port) +
{{ $port }}
+ @endforeach +
+
+ @endif + + @if (isset($imageDetails[0]['Config']['Volumes']) && !empty($imageDetails[0]['Config']['Volumes'])) +
+
Volumes
+
+ @foreach (array_keys($imageDetails[0]['Config']['Volumes']) as $volume) +
{{ $volume }}
+ @endforeach +
+
+ @endif + + @if (isset($imageDetails[0]['Config']['Cmd']) && !empty($imageDetails[0]['Config']['Cmd'])) +
+
Command
+
+ {{ implode(' ', $imageDetails[0]['Config']['Cmd']) }} +
+
+ @endif +
+
+ @endif + +
+ + Prune Unused + + + Delete Image + +
+
+
+
+ @endif