feat(security): implement server patching functionality
- Add CheckUpdates and UpdatePackage actions for managing server updates. - Create ServerPackageUpdated event for broadcasting update status. - Introduce Patches Livewire component for user interface to check and apply updates. - Update navigation and sidebar to include security patching options.
This commit is contained in:
		
							
								
								
									
										227
									
								
								app/Actions/Server/CheckUpdates.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										227
									
								
								app/Actions/Server/CheckUpdates.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,227 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Actions\Server; | ||||||
|  | 
 | ||||||
|  | use App\Models\Server; | ||||||
|  | use Lorisleiva\Actions\Concerns\AsAction; | ||||||
|  | 
 | ||||||
|  | class CheckUpdates | ||||||
|  | { | ||||||
|  |     use AsAction; | ||||||
|  | 
 | ||||||
|  |     public string $jobQueue = 'high'; | ||||||
|  | 
 | ||||||
|  |     public function handle(Server $server) | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             if ($server->serverStatus() === false) { | ||||||
|  |                 return [ | ||||||
|  |                     'error' => 'Server is not reachable or not ready.', | ||||||
|  |                 ]; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Try first method - using instant_remote_process
 | ||||||
|  |             $output = instant_remote_process(['cat /etc/os-release'], $server); | ||||||
|  | 
 | ||||||
|  |             // Parse os-release into an associative array
 | ||||||
|  |             $osInfo = []; | ||||||
|  |             foreach (explode("\n", $output) as $line) { | ||||||
|  |                 if (empty($line)) { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 if (strpos($line, '=') === false) { | ||||||
|  |                     continue; | ||||||
|  |                 } | ||||||
|  |                 [$key, $value] = explode('=', $line, 2); | ||||||
|  |                 $osInfo[$key] = trim($value, '"'); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Get the main OS identifier
 | ||||||
|  |             $osId = $osInfo['ID'] ?? ''; | ||||||
|  |             // $osIdLike = $osInfo['ID_LIKE'] ?? '';
 | ||||||
|  |             // $versionId = $osInfo['VERSION_ID'] ?? '';
 | ||||||
|  | 
 | ||||||
|  |             // Normalize OS types based on install.sh logic
 | ||||||
|  |             switch ($osId) { | ||||||
|  |                 case 'manjaro': | ||||||
|  |                 case 'manjaro-arm': | ||||||
|  |                 case 'endeavouros': | ||||||
|  |                     $osType = 'arch'; | ||||||
|  |                     break; | ||||||
|  |                 case 'pop': | ||||||
|  |                 case 'linuxmint': | ||||||
|  |                 case 'zorin': | ||||||
|  |                     $osType = 'ubuntu'; | ||||||
|  |                     break; | ||||||
|  |                 case 'fedora-asahi-remix': | ||||||
|  |                     $osType = 'fedora'; | ||||||
|  |                     break; | ||||||
|  |                 default: | ||||||
|  |                     $osType = $osId; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Determine package manager based on OS type
 | ||||||
|  |             $packageManager = match ($osType) { | ||||||
|  |                 'arch' => 'pacman', | ||||||
|  |                 'alpine' => 'apk', | ||||||
|  |                 'ubuntu', 'debian', 'raspbian' => 'apt', | ||||||
|  |                 'centos', 'fedora', 'rhel', 'ol', 'rocky', 'almalinux', 'amzn' => 'dnf', | ||||||
|  |                 'sles', 'opensuse-leap', 'opensuse-tumbleweed' => 'zypper', | ||||||
|  |                 default => null | ||||||
|  |             }; | ||||||
|  | 
 | ||||||
|  |             switch ($packageManager) { | ||||||
|  |                 case 'zypper': | ||||||
|  |                     $output = instant_remote_process(['LANG=C zypper -tx list-updates'], $server); | ||||||
|  |                     $out = $this->parseZypperOutput($output); | ||||||
|  |                     $out['osId'] = $osId; | ||||||
|  |                     $out['package_manager'] = $packageManager; | ||||||
|  | 
 | ||||||
|  |                     return $out; | ||||||
|  |                 case 'dnf': | ||||||
|  |                     $output = instant_remote_process(['LANG=C dnf list -q --updates --refresh'], $server); | ||||||
|  |                     $out = $this->parseDnfOutput($output); | ||||||
|  |                     $out['osId'] = $osId; | ||||||
|  |                     $out['package_manager'] = $packageManager; | ||||||
|  |                     $rebootRequired = instant_remote_process(['LANG=C dnf needs-restarting -r'], $server); | ||||||
|  |                     $out['reboot_required'] = $rebootRequired === '0' ? true : false; | ||||||
|  | 
 | ||||||
|  |                     return $out; | ||||||
|  |                 case 'apt': | ||||||
|  |                     instant_remote_process(['apt-get update -qq'], $server); | ||||||
|  |                     $output = instant_remote_process(['LANG=C apt list --upgradable 2>/dev/null'], $server); | ||||||
|  | 
 | ||||||
|  |                     $out = $this->parseAptOutput($output); | ||||||
|  |                     $out['osId'] = $osId; | ||||||
|  |                     $out['package_manager'] = $packageManager; | ||||||
|  |                     $rebootRequired = instant_remote_process(['LANG=C test -f /var/run/reboot-required && echo "YES" || echo "NO"'], $server); | ||||||
|  |                     $out['reboot_required'] = $rebootRequired === 'YES' ? true : false; | ||||||
|  | 
 | ||||||
|  |                     return $out; | ||||||
|  |                 default: | ||||||
|  |                     return [ | ||||||
|  |                         'osId' => $osId, | ||||||
|  |                         'error' => 'Unsupported package manager', | ||||||
|  |                         'package_manager' => $packageManager, | ||||||
|  |                     ]; | ||||||
|  |             } | ||||||
|  |         } catch (\Throwable $e) { | ||||||
|  |             ray('Error:', $e->getMessage()); | ||||||
|  | 
 | ||||||
|  |             return [ | ||||||
|  |                 'osId' => $osId, | ||||||
|  |                 'package_manager' => $packageManager, | ||||||
|  |                 'error' => $e->getMessage(), | ||||||
|  |                 'trace' => $e->getTraceAsString(), | ||||||
|  |             ]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function parseZypperOutput(string $output): array | ||||||
|  |     { | ||||||
|  |         $updates = []; | ||||||
|  | 
 | ||||||
|  |         try { | ||||||
|  |             $xml = simplexml_load_string($output); | ||||||
|  |             if ($xml === false) { | ||||||
|  |                 return [ | ||||||
|  |                     'total_updates' => 0, | ||||||
|  |                     'updates' => [], | ||||||
|  |                     'error' => 'Failed to parse XML output', | ||||||
|  |                 ]; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Navigate to the update-list node
 | ||||||
|  |             $updateList = $xml->xpath('//update-list/update'); | ||||||
|  | 
 | ||||||
|  |             foreach ($updateList as $update) { | ||||||
|  |                 $updates[] = [ | ||||||
|  |                     'package' => (string) $update['name'], | ||||||
|  |                     'new_version' => (string) $update['edition'], | ||||||
|  |                     'current_version' => (string) $update['edition-old'], | ||||||
|  |                     'architecture' => (string) $update['arch'], | ||||||
|  |                     'repository' => (string) $update->source['alias'], | ||||||
|  |                     'summary' => (string) $update->summary, | ||||||
|  |                     'description' => (string) $update->description, | ||||||
|  |                 ]; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return [ | ||||||
|  |                 'total_updates' => count($updates), | ||||||
|  |                 'updates' => $updates, | ||||||
|  |             ]; | ||||||
|  |         } catch (\Throwable $e) { | ||||||
|  |             return [ | ||||||
|  |                 'total_updates' => 0, | ||||||
|  |                 'updates' => [], | ||||||
|  |                 'error' => 'Error parsing zypper output: '.$e->getMessage(), | ||||||
|  |             ]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function parseDnfOutput(string $output): array | ||||||
|  |     { | ||||||
|  |         $updates = []; | ||||||
|  |         $lines = explode("\n", $output); | ||||||
|  | 
 | ||||||
|  |         foreach ($lines as $line) { | ||||||
|  |             if (empty($line)) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Split by multiple spaces/tabs and filter out empty elements
 | ||||||
|  |             $parts = array_values(array_filter(preg_split('/\s+/', $line))); | ||||||
|  | 
 | ||||||
|  |             if (count($parts) >= 3) { | ||||||
|  |                 $package = $parts[0]; | ||||||
|  |                 $new_version = $parts[1]; | ||||||
|  |                 $repository = $parts[2]; | ||||||
|  | 
 | ||||||
|  |                 // Extract architecture from package name (e.g., "cloud-init.noarch" -> "noarch")
 | ||||||
|  |                 $architecture = str_contains($package, '.') ? explode('.', $package)[1] : 'noarch'; | ||||||
|  | 
 | ||||||
|  |                 $updates[] = [ | ||||||
|  |                     'package' => $package, | ||||||
|  |                     'new_version' => $new_version, | ||||||
|  |                     'repository' => $repository, | ||||||
|  |                     'architecture' => $architecture, | ||||||
|  |                     'current_version' => 'unknown', // DNF doesn't show current version in check-update output
 | ||||||
|  |                 ]; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return [ | ||||||
|  |             'total_updates' => count($updates), | ||||||
|  |             'updates' => $updates, | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function parseAptOutput(string $output): array | ||||||
|  |     { | ||||||
|  |         $updates = []; | ||||||
|  |         $lines = explode("\n", $output); | ||||||
|  | 
 | ||||||
|  |         foreach ($lines as $line) { | ||||||
|  |             // Skip the "Listing... Done" line and empty lines
 | ||||||
|  |             if (empty($line) || str_contains($line, 'Listing...')) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Example line: package/stable 2.0-1 amd64 [upgradable from: 1.0-1]
 | ||||||
|  |             if (preg_match('/^(.+?)\/(\S+)\s+(\S+)\s+(\S+)\s+\[upgradable from: (.+?)\]/', $line, $matches)) { | ||||||
|  |                 $updates[] = [ | ||||||
|  |                     'package' => $matches[1], | ||||||
|  |                     'repository' => $matches[2], | ||||||
|  |                     'new_version' => $matches[3], | ||||||
|  |                     'architecture' => $matches[4], | ||||||
|  |                     'current_version' => $matches[5], | ||||||
|  |                 ]; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return [ | ||||||
|  |             'total_updates' => count($updates), | ||||||
|  |             'updates' => $updates, | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										81
									
								
								app/Actions/Server/UpdatePackage.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								app/Actions/Server/UpdatePackage.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,81 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Actions\Server; | ||||||
|  | 
 | ||||||
|  | use App\Models\Server; | ||||||
|  | use Lorisleiva\Actions\Concerns\AsAction; | ||||||
|  | use Spatie\Activitylog\Contracts\Activity; | ||||||
|  | 
 | ||||||
|  | class UpdatePackage | ||||||
|  | { | ||||||
|  |     use AsAction; | ||||||
|  | 
 | ||||||
|  |     public string $jobQueue = 'high'; | ||||||
|  | 
 | ||||||
|  |     public function handle(Server $server, string $osId, ?string $package = null, ?string $packageManager = null, bool $all = false): Activity|array | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             if ($server->serverStatus() === false) { | ||||||
|  |                 return [ | ||||||
|  |                     'error' => 'Server is not reachable or not ready.', | ||||||
|  |                 ]; | ||||||
|  |             } | ||||||
|  |             switch ($packageManager) { | ||||||
|  |                 case 'zypper': | ||||||
|  |                     $commandAll = 'zypper update -y'; | ||||||
|  |                     $commandInstall = 'zypper install -y '.$package; | ||||||
|  |                     break; | ||||||
|  |                 case 'dnf': | ||||||
|  |                     $commandAll = 'dnf update -y'; | ||||||
|  |                     $commandInstall = 'dnf update -y '.$package; | ||||||
|  |                     break; | ||||||
|  |                 case 'apt': | ||||||
|  |                     $commandAll = 'apt update && apt upgrade -y'; | ||||||
|  |                     $commandInstall = 'apt install -y '.$package; | ||||||
|  |                     break; | ||||||
|  |                 default: | ||||||
|  |                     return [ | ||||||
|  |                         'error' => 'OS not supported', | ||||||
|  |                     ]; | ||||||
|  |             } | ||||||
|  |             if ($all) { | ||||||
|  |                 return remote_process([$commandAll], $server); | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             return remote_process([$commandInstall], $server); | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             return [ | ||||||
|  |                 'error' => $e->getMessage(), | ||||||
|  |             ]; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private function parseAptOutput(string $output): array | ||||||
|  |     { | ||||||
|  |         $updates = []; | ||||||
|  |         $lines = explode("\n", $output); | ||||||
|  | 
 | ||||||
|  |         foreach ($lines as $line) { | ||||||
|  |             // Skip the "Listing... Done" line and empty lines
 | ||||||
|  |             if (empty($line) || str_contains($line, 'Listing...')) { | ||||||
|  |                 continue; | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             // Example line: package/stable 2.0-1 amd64 [upgradable from: 1.0-1]
 | ||||||
|  |             if (preg_match('/^(.+?)\/(\S+)\s+(\S+)\s+(\S+)\s+\[upgradable from: (.+?)\]/', $line, $matches)) { | ||||||
|  |                 $updates[] = [ | ||||||
|  |                     'package' => $matches[1], | ||||||
|  |                     'repository' => $matches[2], | ||||||
|  |                     'new_version' => $matches[3], | ||||||
|  |                     'architecture' => $matches[4], | ||||||
|  |                     'current_version' => $matches[5], | ||||||
|  |                 ]; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return [ | ||||||
|  |             'total_updates' => count($updates), | ||||||
|  |             'updates' => $updates, | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										35
									
								
								app/Events/ServerPackageUpdated.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								app/Events/ServerPackageUpdated.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Events; | ||||||
|  | 
 | ||||||
|  | use Illuminate\Broadcasting\InteractsWithSockets; | ||||||
|  | use Illuminate\Broadcasting\PrivateChannel; | ||||||
|  | use Illuminate\Contracts\Broadcasting\ShouldBroadcast; | ||||||
|  | use Illuminate\Foundation\Events\Dispatchable; | ||||||
|  | use Illuminate\Queue\SerializesModels; | ||||||
|  | 
 | ||||||
|  | class ServerPackageUpdated implements ShouldBroadcast | ||||||
|  | { | ||||||
|  |     use Dispatchable, InteractsWithSockets, SerializesModels; | ||||||
|  | 
 | ||||||
|  |     public ?int $teamId = null; | ||||||
|  | 
 | ||||||
|  |     public function __construct($teamId = null) | ||||||
|  |     { | ||||||
|  |         if (is_null($teamId) && auth()->check() && auth()->user()->currentTeam()) { | ||||||
|  |             $teamId = auth()->user()->currentTeam()->id; | ||||||
|  |         } | ||||||
|  |         $this->teamId = $teamId; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function broadcastOn(): array | ||||||
|  |     { | ||||||
|  |         if (is_null($this->teamId)) { | ||||||
|  |             return []; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         return [ | ||||||
|  |             new PrivateChannel("team.{$this->teamId}"), | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										92
									
								
								app/Livewire/Server/Security/Patches.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								app/Livewire/Server/Security/Patches.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,92 @@ | |||||||
|  | <?php | ||||||
|  | 
 | ||||||
|  | namespace App\Livewire\Server\Security; | ||||||
|  | 
 | ||||||
|  | use App\Actions\Server\CheckUpdates; | ||||||
|  | use App\Actions\Server\UpdatePackage; | ||||||
|  | use App\Events\ServerPackageUpdated; | ||||||
|  | use App\Models\Server; | ||||||
|  | use Livewire\Component; | ||||||
|  | 
 | ||||||
|  | class Patches extends Component | ||||||
|  | { | ||||||
|  |     public array $parameters; | ||||||
|  | 
 | ||||||
|  |     public Server $server; | ||||||
|  | 
 | ||||||
|  |     public ?int $totalUpdates = null; | ||||||
|  | 
 | ||||||
|  |     public ?array $updates = null; | ||||||
|  | 
 | ||||||
|  |     public ?string $error = null; | ||||||
|  | 
 | ||||||
|  |     public ?string $osId = null; | ||||||
|  | 
 | ||||||
|  |     public ?string $packageManager = null; | ||||||
|  | 
 | ||||||
|  |     public function getListeners() | ||||||
|  |     { | ||||||
|  |         $teamId = auth()->user()->currentTeam()->id; | ||||||
|  | 
 | ||||||
|  |         return [ | ||||||
|  |             "echo-private:team.{$teamId},ServerPackageUpdated" => 'checkForUpdatesDispatch', | ||||||
|  |         ]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function mount() | ||||||
|  |     { | ||||||
|  |         if (! auth()->user()->isAdmin()) { | ||||||
|  |             abort(403); | ||||||
|  |         } | ||||||
|  |         $this->parameters = get_route_parameters(); | ||||||
|  |         $this->server = Server::ownedByCurrentTeam()->whereUuid($this->parameters['server_uuid'])->firstOrFail(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function checkForUpdatesDispatch() | ||||||
|  |     { | ||||||
|  |         $this->totalUpdates = null; | ||||||
|  |         $this->updates = null; | ||||||
|  |         $this->error = null; | ||||||
|  |         $this->osId = null; | ||||||
|  |         $this->packageManager = null; | ||||||
|  |         $this->dispatch('checkForUpdatesDispatch'); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function checkForUpdates() | ||||||
|  |     { | ||||||
|  |         $job = CheckUpdates::run($this->server); | ||||||
|  |         if (isset($job['error'])) { | ||||||
|  |             $this->error = data_get($job, 'error', 'Something went wrong.'); | ||||||
|  |         } else { | ||||||
|  |             $this->totalUpdates = data_get($job, 'total_updates', 0); | ||||||
|  |             $this->updates = data_get($job, 'updates', []); | ||||||
|  |             $this->osId = data_get($job, 'osId', null); | ||||||
|  |             $this->packageManager = data_get($job, 'package_manager', null); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function updateAllPackages() | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             $activity = UpdatePackage::run(server: $this->server, packageManager: $this->packageManager, osId: $this->osId, all: true); | ||||||
|  |             $this->dispatch('activityMonitor', $activity->id, ServerPackageUpdated::class); | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             $this->dispatch('error', message: $e->getMessage()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function updatePackage($package) | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             $activity = UpdatePackage::run(server: $this->server, packageManager: $this->packageManager, osId: $this->osId, package: $package); | ||||||
|  |             $this->dispatch('activityMonitor', $activity->id, ServerPackageUpdated::class); | ||||||
|  |         } catch (\Exception $e) { | ||||||
|  |             $this->dispatch('error', message: $e->getMessage()); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public function render() | ||||||
|  |     { | ||||||
|  |         return view('livewire.server.security.patches'); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -45,6 +45,12 @@ | |||||||
|                 ]) }}">
 |                 ]) }}">
 | ||||||
|                 <button>Terminal</button> |                 <button>Terminal</button> | ||||||
|             </a> |             </a> | ||||||
|  |             <a class="{{ request()->routeIs('server.security.patches') ? 'dark:text-white' : '' }}" | ||||||
|  |                 href="{{ route('server.security.patches', [
 | ||||||
|  |                     'server_uuid' => data_get($server, 'uuid'), | ||||||
|  |                 ]) }}">
 | ||||||
|  |                 <button>Security</button> | ||||||
|  |             </a> | ||||||
|         </nav> |         </nav> | ||||||
|         <div class="order-first sm:order-last"> |         <div class="order-first sm:order-last"> | ||||||
|             <livewire:server.proxy.deploy :server="$server" /> |             <livewire:server.proxy.deploy :server="$server" /> | ||||||
|   | |||||||
| @@ -0,0 +1,6 @@ | |||||||
|  | <div class="flex flex-col items-start gap-2 min-w-fit"> | ||||||
|  |     <a class="{{ request()->routeIs('server.security.patches') ? 'menu-item menu-item-active' : 'menu-item' }}" | ||||||
|  |         href="{{ route('server.security.patches', $parameters) }}"> | ||||||
|  |         <button>Server Patching</button> | ||||||
|  |     </a> | ||||||
|  | </div> | ||||||
							
								
								
									
										118
									
								
								resources/views/livewire/server/security/patches.blade.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										118
									
								
								resources/views/livewire/server/security/patches.blade.php
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,118 @@ | |||||||
|  | <div> | ||||||
|  |     <x-slot:title> | ||||||
|  |         {{ data_get_str($server, 'name')->limit(10) }} > Security | Coolify | ||||||
|  |     </x-slot> | ||||||
|  |     <x-server.navbar :server="$server" /> | ||||||
|  |     <x-slide-over closeWithX fullScreen @startupdate.window="slideOverOpen = true"> | ||||||
|  |         <x-slot:title>Updating Packages</x-slot:title> | ||||||
|  |         <x-slot:content> | ||||||
|  |             <livewire:activity-monitor header="Logs" /> | ||||||
|  |         </x-slot:content> | ||||||
|  |     </x-slide-over> | ||||||
|  | 
 | ||||||
|  |     <div x-data="{ activeTab: window.location.hash ? window.location.hash.substring(1) : 'general' }" class="flex flex-col h-full gap-8 sm:flex-row"> | ||||||
|  |         <x-server.sidebar-security :server="$server" :parameters="$parameters" /> | ||||||
|  |         <form wire:submit='submit' class="w-full"> | ||||||
|  |             <div> | ||||||
|  |                 <div class="flex items-center gap-2"> | ||||||
|  |                     <h2>Server Patching</h2> | ||||||
|  |                     <x-forms.button type="button" wire:click="$dispatch('checkForUpdatesDispatch')">Manually | ||||||
|  |                         Check</x-forms.button> | ||||||
|  |                 </div> | ||||||
|  |                 <div>Check if your server has updates available.</div> | ||||||
|  |                 <div class="text-xs pt-1">(only available for apt, dnf and zypper package managers atm, more coming | ||||||
|  |                     soon as well as more features...) | ||||||
|  |                 </div> | ||||||
|  |                 <div> | ||||||
|  |                     <div class="flex flex-col gap-6 pt-4"> | ||||||
|  |                         <div class="flex flex-col"> | ||||||
|  |                             <div> | ||||||
|  |                                 <div wire:target="checkForUpdates" wire:loading> | ||||||
|  |                                     Checking for updates. It may take a few minutes. <x-loading /> | ||||||
|  |                                 </div> | ||||||
|  |                                 @if ($error) | ||||||
|  |                                     <div class="text-red-500">{{ $error }}</div> | ||||||
|  |                                 @else | ||||||
|  |                                     @if ($totalUpdates === 0) | ||||||
|  |                                         <div class="text-green-500">Your server is up to date.</div> | ||||||
|  |                                     @endif | ||||||
|  |                                     @if (isset($updates) && count($updates) > 0) | ||||||
|  |                                         <x-modal-confirmation title="Confirm package update?" | ||||||
|  |                                             buttonTitle="Update All
 | ||||||
|  |                                             Packages" | ||||||
|  |                                             isHighlightedButton submitAction="updateAllPackages" dispatchAction | ||||||
|  |                                             :actions="[
 | ||||||
|  |                                                 'All packages will be updated to the latest version.', | ||||||
|  |                                                 'This action could restart your currently running containers if docker will be updated.', | ||||||
|  |                                             ]" confirmationText="Update All Packages" | ||||||
|  |                                             confirmationLabel="Please confirm the execution of the actions by entering the name below" | ||||||
|  |                                             shortConfirmationLabel="Name" :confirmWithPassword=false | ||||||
|  |                                             step2ButtonText="Update All
 | ||||||
|  |                                             Packages" />
 | ||||||
|  |                                         <table> | ||||||
|  |                                             <thead> | ||||||
|  |                                                 <tr> | ||||||
|  |                                                     <th>Package</th> | ||||||
|  |                                                     @if ($packageManager !== 'dnf') | ||||||
|  |                                                         <th>Current Version</th> | ||||||
|  |                                                     @endif | ||||||
|  |                                                     <th>New Version</th> | ||||||
|  |                                                     <th>Action</th> | ||||||
|  |                                                 </tr> | ||||||
|  |                                             </thead> | ||||||
|  |                                             <tbody> | ||||||
|  |                                                 @foreach ($updates as $update) | ||||||
|  |                                                     <tr> | ||||||
|  |                                                         <td class="inline-flex gap-2 justify-center items-center"> | ||||||
|  |                                                             @if (data_get_str($update, 'package')->contains('docker')) | ||||||
|  |                                                                 <x-helper :helper="'This package will restart your currently running containers'"> | ||||||
|  |                                                                     <x-slot:icon> | ||||||
|  |                                                                         <svg class="w-4 h-4 text-red-500 block" | ||||||
|  |                                                                             viewBox="0 0 256 256" | ||||||
|  |                                                                             xmlns="http://www.w3.org/2000/svg"> | ||||||
|  |                                                                             <path fill="currentColor" | ||||||
|  |                                                                                 d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16"> | ||||||
|  |                                                                             </path> | ||||||
|  |                                                                         </svg> | ||||||
|  |                                                                     </x-slot:icon> | ||||||
|  |                                                                 </x-helper> | ||||||
|  |                                                             @endif | ||||||
|  |                                                             {{ data_get($update, 'package') }} | ||||||
|  |                                                         </td> | ||||||
|  |                                                         @if ($packageManager !== 'dnf') | ||||||
|  |                                                             <td>{{ data_get($update, 'current_version') }}</td> | ||||||
|  |                                                         @endif | ||||||
|  |                                                         <td>{{ data_get($update, 'new_version') }}</td> | ||||||
|  |                                                         <td> | ||||||
|  |                                                             <x-forms.button type="button" | ||||||
|  |                                                                 wire:click="$dispatch('updatePackage', { package: '{{ data_get($update, 'package') }}' })">Update</x-forms.button> | ||||||
|  |                                                         </td> | ||||||
|  |                                                     </tr> | ||||||
|  |                                                 @endforeach | ||||||
|  |                                             </tbody> | ||||||
|  |                                         </table> | ||||||
|  |                                     @endif | ||||||
|  |                                 @endif | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         </form> | ||||||
|  |     </div> | ||||||
|  |     @script | ||||||
|  |         <script> | ||||||
|  |             $wire.on('updateAllPackages', () => { | ||||||
|  |                 window.dispatchEvent(new CustomEvent('startupdate')); | ||||||
|  |                 $wire.$call('updateAllPackages'); | ||||||
|  |             }); | ||||||
|  |             $wire.on('updatePackage', (data) => { | ||||||
|  |                 window.dispatchEvent(new CustomEvent('startupdate')); | ||||||
|  |                 $wire.$call('updatePackage', data.package); | ||||||
|  |             }); | ||||||
|  |             $wire.on('checkForUpdatesDispatch', () => { | ||||||
|  |                 $wire.$call('checkForUpdates'); | ||||||
|  |             }); | ||||||
|  |         </script> | ||||||
|  |     @endscript | ||||||
|  | </div> | ||||||
| @@ -49,6 +49,7 @@ use App\Livewire\Server\Proxy\DynamicConfigurations as ProxyDynamicConfiguration | |||||||
| use App\Livewire\Server\Proxy\Logs as ProxyLogs; | use App\Livewire\Server\Proxy\Logs as ProxyLogs; | ||||||
| use App\Livewire\Server\Proxy\Show as ProxyShow; | use App\Livewire\Server\Proxy\Show as ProxyShow; | ||||||
| use App\Livewire\Server\Resources as ResourcesShow; | use App\Livewire\Server\Resources as ResourcesShow; | ||||||
|  | use App\Livewire\Server\Security\Patches; | ||||||
| use App\Livewire\Server\Show as ServerShow; | use App\Livewire\Server\Show as ServerShow; | ||||||
| use App\Livewire\Settings\Index as SettingsIndex; | use App\Livewire\Settings\Index as SettingsIndex; | ||||||
| use App\Livewire\SettingsBackup; | use App\Livewire\SettingsBackup; | ||||||
| @@ -252,6 +253,8 @@ Route::middleware(['auth', 'verified'])->group(function () { | |||||||
|         Route::get('/proxy/logs', ProxyLogs::class)->name('server.proxy.logs'); |         Route::get('/proxy/logs', ProxyLogs::class)->name('server.proxy.logs'); | ||||||
|         Route::get('/terminal', ExecuteContainerCommand::class)->name('server.command'); |         Route::get('/terminal', ExecuteContainerCommand::class)->name('server.command'); | ||||||
|         Route::get('/docker-cleanup', DockerCleanup::class)->name('server.docker-cleanup'); |         Route::get('/docker-cleanup', DockerCleanup::class)->name('server.docker-cleanup'); | ||||||
|  |         Route::get('/security', fn () => redirect(route('dashboard'))); | ||||||
|  |         Route::get('/security/patches', Patches::class)->name('server.security.patches'); | ||||||
|     }); |     }); | ||||||
|     Route::get('/destinations', DestinationIndex::class)->name('destination.index'); |     Route::get('/destinations', DestinationIndex::class)->name('destination.index'); | ||||||
|     Route::get('/destination/{destination_uuid}', DestinationShow::class)->name('destination.show'); |     Route::get('/destination/{destination_uuid}', DestinationShow::class)->name('destination.show'); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 Andras Bacsai
					Andras Bacsai