Files
coolify/app/Livewire/ImageScan/ServerImageScanning.php
033f0c84da v4.7 (#12478)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: peaklabs-dev <122374094+peaklabs-dev@users.noreply.github.com>
Co-authored-by: Aditya Tripathi <aditya@climactic.co>
2026-11-06 14:25:02 +01:00

143 lines
5.2 KiB
PHP

<?php
namespace App\Livewire\ImageScan;
use App\Actions\ImageScan\SetServerImageScanning;
use App\Jobs\PrepareImageScannerJob;
use App\Models\InstanceSettings;
use App\Models\Server;
use App\Services\ImageScan\ImageRef;
use App\Services\ImageScan\ImageScanningRefused;
use App\Services\ImageScan\ScannerCommands;
use App\Traits\ListensToTeamChannel;
use Carbon\CarbonImmutable;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Str;
use Livewire\Component;
class ServerImageScanning extends Component
{
use AuthorizesRequests;
use ListensToTeamChannel;
public Server $server;
public function getListeners()
{
return $this->teamChannelListeners([
'ImageScanUpdated' => 'scanUpdated',
]);
}
public function mount(string $server_uuid)
{
$this->server = Server::ownedByCurrentTeam()->whereUuid($server_uuid)->firstOrFail();
$this->authorize('viewSecurity', $this->server);
}
public function scanUpdated(array $event = []): void
{
$resourceUuid = data_get($event, 'resourceUuid');
if ($resourceUuid !== $this->server->uuid || data_get($event, 'scanUuid') !== PrepareImageScannerJob::SCAN_UUID) {
$this->skipRender();
}
}
public function enable()
{
$this->authorize('update', $this->server);
try {
SetServerImageScanning::run($this->server, true);
$this->auditImageScanning('image_scanning_enabled');
$this->dispatch('success', 'Image scanning turned on. Coolify is getting the scanner.');
} catch (ImageScanningRefused $e) {
$this->dispatch('error', message: $e->getMessage());
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function prepareScanner()
{
$this->authorize('update', $this->server);
try {
SetServerImageScanning::ensureManagedHere($this->server);
if (! InstanceSettings::get()->is_image_scanning_allowed || ! $this->server->settings->refresh()->is_image_scan_enabled) {
$this->dispatch('error', message: 'Turn on image scanning first.');
return;
}
SetServerImageScanning::prepareScanner($this->server);
$this->dispatch('success', 'Coolify is getting the scanner.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function disable()
{
$this->authorize('update', $this->server);
try {
SetServerImageScanning::run($this->server, false);
$this->auditImageScanning('image_scanning_disabled');
$this->dispatch('success', 'Image scanning turned off.');
} catch (\Throwable $e) {
return handleError($e, $this);
}
}
public function deleteScannerData()
{
$this->authorize('update', $this->server);
try {
SetServerImageScanning::ensureManagedHere($this->server);
$commands = new ScannerCommands($this->server, Str::lower(Str::random(24)));
instant_remote_process($commands->deleteScannerData(), $this->server, throwError: true, timeout: 120);
PrepareImageScannerJob::forget($this->server);
$this->auditImageScanning('image_scanner_data_deleted');
$this->dispatch('success', 'Scanner data deleted.');
} catch (\Throwable $e) {
return handleError($e, $this, 'Could not delete the scanner data.');
}
}
private function auditImageScanning(string $action): void
{
auditLog("ui.server.{$action}", [
'team_id' => $this->server->team_id,
'server_uuid' => $this->server->uuid,
'server_name' => $this->server->name,
]);
}
public function render()
{
$state = Cache::get(PrepareImageScannerJob::stateCacheKey($this->server));
$builtAt = Cache::get(PrepareImageScannerJob::builtAtCacheKey($this->server));
$failedStep = Cache::get(PrepareImageScannerJob::failedStepCacheKey($this->server));
$diskUsed = Cache::get(PrepareImageScannerJob::diskUsedCacheKey($this->server));
return view('livewire.image-scan.server-image-scanning', [
'allowed' => (bool) InstanceSettings::get()->is_image_scanning_allowed,
'enabled' => (bool) $this->server->settings->is_image_scan_enabled,
'prepareState' => is_string($state) ? $state : null,
'failedStep' => is_string($failedStep) ? $failedStep : null,
'registries' => collect(config('constants.image_scan.images'))
->map(fn (array $image) => ImageRef::parse($image['repo'])->registry)
->unique()
->implode(', '),
'diskUsed' => is_int($diskUsed) ? formatBytes($diskUsed, 1) : null,
'toolVersions' => collect(['syft' => 'Syft', 'grype' => 'Grype', 'regctl' => 'regctl'])
->map(fn (string $name, string $tool) => $name.' '.config("constants.image_scan.images.{$tool}.version"))
->implode(', '),
'builtAt' => is_string($builtAt) ? CarbonImmutable::parse($builtAt) : null,
]);
}
}