proxmoxServer = ProxmoxServer::with(['os', 'node', 'ipAddresses'])->where('service_id', $this->service->id)->first(); // Fetch actual VM status from Proxmox API if server exists if ($this->proxmoxServer) { $this->loadVmStatus(); } // Only fetch the actions if the service is active if ($this->service->status == Service::STATUS_ACTIVE) { $actions = []; try { $actions = ExtensionHelper::getActions($this->service); } catch (\Exception $e) { } // separate the actions into buttons and views foreach ($actions as $action) { if ($action['type'] == 'button') { $this->buttons[] = $action; } elseif ($action['type'] == 'view') { $this->views[] = $action; } } $this->currentView = $this->currentView ?? ($this->views[0]['name'] ?? null); } } public function changeView($view) { if (!$view) { return; } if ($this->currentView === $view || !in_array($view, array_column($this->views, 'name'))) { return $this->skipRender(); } $this->currentView = $view; } public function updatedShowCancel($value) { if (!$this->service->cancellable) { $this->notify('This service cannot be cancelled', 'error'); $this->showCancel = false; return; } } public function goto($function) { // Check if function is allowed if (!in_array($function, array_column($this->buttons, 'function'))) { $this->notify('This action is not allowed', 'error'); return; } $result = ExtensionHelper::callService($this->service, $function); // If its a response, return it if (!is_string($result)) { return $result; } $this->redirect($result); } /** * Load the actual VM status from Proxmox API. */ protected function loadVmStatus(): void { try { if (!$this->proxmoxServer || !$this->proxmoxServer->node) { return; } $proxmox = new Proxmox(); $data = $proxmox->request( '/nodes/' . $this->proxmoxServer->node->name . '/qemu/' . $this->proxmoxServer->vm_id . '/status/current' )->json()['data'] ?? null; $this->vmStatus = $data; // Also fetch VM config to calculate accurate total storage // The status endpoint's maxdisk is often unreliable if ($this->vmStatus) { $config = $proxmox->request( '/nodes/' . $this->proxmoxServer->node->name . '/qemu/' . $this->proxmoxServer->vm_id . '/config' )->json()['data'] ?? null; if ($config) { $totalDiskGB = $this->calculateTotalStorage($config); if ($totalDiskGB > 0) { // Store as bytes for consistency with other values $this->vmStatus['total_disk_bytes'] = $totalDiskGB * 1024 * 1024 * 1024; } } } } catch (\Throwable $e) { $this->vmStatus = null; } } /** * Calculate total storage from VM config (excluding cloud-init, EFI, TPM). * Returns size in GB. */ protected function calculateTotalStorage(array $config): float { $totalSize = 0; foreach ($config as $key => $value) { // Check for attached drives (ide, sata, scsi, virtio) if (preg_match('/^(ide|sata|scsi|virtio)(\d+)$/', $key)) { // Skip excluded disks (cloud-init, EFI, TPM) if ($this->isExcludedDisk($value)) { continue; } // Skip CD-ROM drives if (str_contains($value, 'media=cdrom')) { continue; } $totalSize += $this->extractDiskSize($value); } } return $totalSize; } /** * Check if a disk should be excluded (cloud-init, EFI, TPM). */ protected function isExcludedDisk(string $value): bool { $excludePatterns = ['cloudinit', 'vm-\d+-cloudinit', 'efidisk', 'tpmstate']; foreach ($excludePatterns as $pattern) { if (preg_match('/' . $pattern . '/i', $value)) { return true; } } return false; } /** * Extract disk size in GB from a disk configuration string. */ protected function extractDiskSize(string $value): float { // Example: cephpool:vm-100-disk-0,size=32G,cache=none,ssd=1 $parts = explode(',', $value); foreach ($parts as $part) { if (preg_match('/^size=(\d+)([KMGT]?)$/i', $part, $sizeMatch)) { $size = (float) $sizeMatch[1]; $unit = strtoupper($sizeMatch[2] ?? 'G'); switch ($unit) { case 'K': return $size / (1024 * 1024); case 'M': return $size / 1024; case 'G': case '': return $size; case 'T': return $size * 1024; } } } return 0; } public function render() { $view = null; $previousView = $this->currentView; if ($this->currentView) { try { // Search array for the current view $currentViewObj = $this->views[array_search($this->currentView, array_column($this->views, 'name'))] ?? null; if (!$currentViewObj) { throw new \Exception('View not found'); } $view = ExtensionHelper::getView($this->service, $currentViewObj); } catch (\Exception $e) { if ($previousView !== $this->views[0]['name'] ?? null) { $this->notify('Got an error while trying to load the view', 'error'); } $this->currentView = $this->views[0]['name'] ?? null; } } return view('services.show', ['extensionView' => $view])->layoutData([ 'title' => 'Services', 'sidebar' => true, ]); } }