diff --git a/extensions/Servers/Proxmox/Livewire/Storage.php b/extensions/Servers/Proxmox/Livewire/Storage.php index e9609aabb..ce529999e 100644 --- a/extensions/Servers/Proxmox/Livewire/Storage.php +++ b/extensions/Servers/Proxmox/Livewire/Storage.php @@ -236,10 +236,10 @@ class Storage extends Component }); $this->drives = $drives; - $this->totalAllocatedStorage = $totalSize; // Parse unused/detached drives $unusedDrives = []; + $unusedTotalSize = 0; foreach ($config as $key => $value) { if (!preg_match('/^unused(\d+)$/', $key, $matches)) { continue; @@ -254,6 +254,7 @@ class Storage extends Component $unusedInfo = $this->parseUnusedDiskString($value, $unusedIndex); if ($unusedInfo) { $unusedDrives[$key] = $unusedInfo; + $unusedTotalSize += $unusedInfo['size'] ?? 0; } } @@ -265,6 +266,9 @@ class Storage extends Component }); $this->unusedDrives = $unusedDrives; + + // Total storage includes both attached and detached drives + $this->totalAllocatedStorage = $totalSize + $unusedTotalSize; } /** @@ -276,12 +280,45 @@ class Storage extends Component // Unused disks typically just have the volume reference $volume = trim($value); - // Try to extract size from volume name or fetch from storage - // For now, we'll mark size as unknown until reattached + // Extract storage name from volume (format: storage:volume-name) + $storageName = 'cephpool'; + if (str_contains($volume, ':')) { + [$storageName, ] = explode(':', $volume, 2); + } + + // Try to get the volume size from the storage API + $sizeGb = 0; + $sizeFormatted = 'Unknown'; + try { + $proxmox = new Proxmox($this->server->node->server); + $storageContent = $proxmox->request( + '/nodes/' . $this->server->node->name . '/storage/' . $storageName . '/content', + data: ['vmid' => $this->server->vmid] + ); + + if (isset($storageContent['data']) && is_array($storageContent['data'])) { + foreach ($storageContent['data'] as $item) { + if (isset($item['volid']) && $item['volid'] === $volume) { + // Size is in bytes + if (isset($item['size'])) { + $sizeGb = round($item['size'] / (1024 * 1024 * 1024), 2); + $sizeFormatted = $sizeGb . ' GB'; + } + break; + } + } + } + } catch (\Exception $e) { + // If we can't fetch size, leave as unknown + } + return [ 'key' => 'unused' . $unusedIndex, 'index' => $unusedIndex, 'volume' => $volume, + 'storage' => $storageName, + 'size' => $sizeGb, + 'size_formatted' => $sizeFormatted, 'label' => 'Detached Disk ' . $unusedIndex, ]; } @@ -881,6 +918,129 @@ class Storage extends Component } } + /** + * Open the reattach modal for an unused drive. + */ + public function openReattachModal(string $unusedKey): void + { + if (!isset($this->unusedDrives[$unusedKey])) { + $this->notify('Unused drive not found.', 'error'); + return; + } + + $this->reattachingDriveKey = $unusedKey; + $this->reattachTargetBus = 'scsi'; + $this->showReattachModal = true; + } + + /** + * Close the reattach modal. + */ + public function closeReattachModal(): void + { + $this->showReattachModal = false; + $this->reattachingDriveKey = null; + } + + /** + * Reattach an unused drive to a bus. + */ + public function reattachDrive(): void + { + if (!$this->reattachingDriveKey || !isset($this->unusedDrives[$this->reattachingDriveKey])) { + $this->notify('Unused drive not found.', 'error'); + return; + } + + // Check VM status + if ($this->status && $this->status->status !== 'stopped') { + $this->notify('VM must be stopped to reattach drives.', 'error'); + return; + } + + $unusedDrive = $this->unusedDrives[$this->reattachingDriveKey]; + $targetBus = $this->reattachTargetBus; + + // Get next available index on target bus + $targetIndex = $this->getNextAvailableIndex($targetBus); + if ($targetIndex === null) { + $this->notify("No available slots for {$this->busTypes[$targetBus]['label']} drives.", 'error'); + return; + } + + $targetKey = $targetBus . $targetIndex; + + try { + $proxmox = new Proxmox; + + // Build the disk configuration + $configParts = [$unusedDrive['volume']]; + + // Add default settings for the bus type + $configParts[] = 'cache=none'; + $configParts[] = 'aio=io_uring'; + + // Add IO thread for supported bus types + if ($this->busTypes[$targetBus]['iothread_support']) { + $configParts[] = 'iothread=1'; + } + + // Add discard + $configParts[] = 'discard=on'; + + $diskConfig = implode(',', $configParts); + + // Set the disk on the new bus and delete the unused reference + $proxmox->request( + '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + 'put', + [ + $targetKey => $diskConfig, + 'delete' => $this->reattachingDriveKey, + ] + ); + + $this->notify("Drive reattached as {$targetKey} successfully.", 'success'); + $this->closeReattachModal(); + $this->loadStorageConfiguration(false); + } catch (\Exception $e) { + $this->notify('Failed to reattach drive: ' . $e->getMessage(), 'error'); + } + } + + /** + * Delete an unused drive permanently. + */ + public function deleteUnusedDrive(string $unusedKey): void + { + if (!isset($this->unusedDrives[$unusedKey])) { + $this->notify('Unused drive not found.', 'error'); + return; + } + + // Check VM status + if ($this->status && $this->status->status !== 'stopped') { + $this->notify('VM must be stopped to delete drives.', 'error'); + return; + } + + try { + $proxmox = new Proxmox; + $proxmox->request( + '/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config', + 'put', + [ + 'delete' => $unusedKey, + ] + ); + + $this->notify("Unused drive deleted successfully.", 'success'); + $this->loadStorageConfiguration(false); + } catch (\Exception $e) { + $this->notify('Failed to delete unused drive: ' . $e->getMessage(), 'error'); + } + } + /** * Save the SCSI controller type. */ diff --git a/extensions/Servers/Proxmox/views/storage.blade.php b/extensions/Servers/Proxmox/views/storage.blade.php index 812adc769..43577d264 100644 --- a/extensions/Servers/Proxmox/views/storage.blade.php +++ b/extensions/Servers/Proxmox/views/storage.blade.php @@ -103,6 +103,9 @@
{{ number_format($totalAllocatedStorage, 1) }} GB used of {{ number_format($maxAllowedStorage, 1) }} GB allocated + @if(count($unusedDrives) > 0) + (includes detached drives) + @endif
These drives have been detached but their data is preserved. You can reattach them or delete them permanently.
+{{ $reattachingDriveKey }}
++ {{ $unusedDrives[$reattachingDriveKey]['size_formatted'] ?? 'Unknown size' }} +
+This will reattach the drive to the selected bus position.
+