feat: implement reattach and delete functionality for unused drives with UI updates

This commit is contained in:
2025-12-05 04:34:09 -05:00
parent dfb1de9abf
commit adc4632e24
2 changed files with 338 additions and 3 deletions

View File

@@ -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.
*/