feat: Update Proxmox extension with various improvements and bug fixes

- Change listStates method to accept nullable integer for limit parameter.
- Modify TechnitiumDns class to return structured error messages on failure.
- Enhance VNC and xterm popup routes with user authorization checks.
- Update backups view to improve loading states and add unique keys for dynamic elements.
- Refactor cloud-init view to ensure proper data binding and improve user experience.
- Add wire:key attributes to various elements in views for better Livewire performance.
- Improve error handling and user feedback in the firewall view.
- Update server overview and statistics views to handle potential null values gracefully.
- Add new migration files to extend database schema with user-set icons and notes for Proxmox nodes.
This commit is contained in:
2026-02-23 17:23:01 -05:00
parent 9aff64be93
commit f394483daa
48 changed files with 442 additions and 268 deletions

View File

@@ -296,8 +296,9 @@ class Storage extends Component
data: ['vmid' => $this->server->vm_id]
);
if (isset($storageContent['data']) && is_array($storageContent['data'])) {
foreach ($storageContent['data'] as $item) {
$data = $storageContent->json()['data'] ?? [];
if (is_array($data)) {
foreach ($data as $item) {
if (isset($item['volid']) && $item['volid'] === $volume) {
// Size is in bytes
if (isset($item['size'])) {
@@ -553,7 +554,7 @@ class Storage extends Component
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config',
'put',
[
$driveKey => $this->storagePool . ':' . $this->newDriveSize . ',' . $driveConfig,
$driveKey => $this->storagePool . ':' . $this->newDriveSize . ($driveConfig ? ',' . $driveConfig : ''),
]
);
@@ -769,7 +770,7 @@ class Storage extends Component
$drive = $this->drives[$driveKey];
// Check VM status - must be stopped to detach
if ($this->status && $this->status->status !== 'stopped') {
if (!$this->status || $this->status->status !== 'stopped') {
$this->notify('VM must be stopped to detach drives.', 'error');
return;
}
@@ -782,9 +783,11 @@ class Storage extends Component
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config'
);
$configData = $config->json()['data'] ?? [];
// Find the next available unused slot
$unusedIndex = 0;
while (isset($config['data']['unused' . $unusedIndex])) {
while (isset($configData['unused' . $unusedIndex])) {
$unusedIndex++;
}
@@ -829,7 +832,7 @@ class Storage extends Component
}
// Check VM status
if ($this->status && $this->status->status !== 'stopped') {
if (!$this->status || $this->status->status !== 'stopped') {
$this->notify('VM must be stopped to change device bus.', 'error');
return;
}
@@ -850,6 +853,7 @@ class Storage extends Component
$config = $proxmox->request(
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config'
);
$configData = $config->json()['data'] ?? [];
// Find the next available slot on the target bus by checking actual Proxmox config
$maxIndex = $this->busTypes[$targetBus]['max'] ?? 4;
@@ -858,7 +862,7 @@ class Storage extends Component
for ($i = 0; $i < $maxIndex; $i++) {
$slotKey = $targetBus . $i;
// Check if this slot is occupied in the actual config
if (!isset($config['data'][$slotKey])) {
if (!isset($configData[$slotKey])) {
$targetIndex = $i;
break;
}
@@ -873,7 +877,7 @@ class Storage extends Component
// Find the next available unused slot
$unusedIndex = 0;
while (isset($config['data']['unused' . $unusedIndex])) {
while (isset($configData['unused' . $unusedIndex])) {
$unusedIndex++;
}
@@ -988,7 +992,7 @@ class Storage extends Component
$driveKey = $this->deletingDriveKey;
// Check VM status
if ($this->status && $this->status->status !== 'stopped') {
if (!$this->status || $this->status->status !== 'stopped') {
$this->notify('VM must be stopped to delete drives.', 'error');
return;
}
@@ -1068,7 +1072,7 @@ class Storage extends Component
}
// Check VM status
if ($this->status && $this->status->status !== 'stopped') {
if (!$this->status || $this->status->status !== 'stopped') {
$this->notify('VM must be stopped to reattach drives.', 'error');
return;
}
@@ -1076,17 +1080,10 @@ class Storage extends Component
$unusedDrive = $this->unusedDrives[$this->reattachingDriveKey];
$targetBus = $this->reattachTargetBus;
// Check if reattaching this drive would exceed the storage quota
// Calculate attached drives total (excluding the detached drive being reattached)
$attachedDrivesTotal = 0;
foreach ($this->drives as $drive) {
$attachedDrivesTotal += $drive['size'] ?? 0;
}
// The drive being reattached is already counted in totalAllocatedStorage (which includes unused drives)
// So we just need to check if the total meets or exceeds the max
if ($this->totalAllocatedStorage >= $this->maxAllowedStorage && $this->maxAllowedStorage > 0) {
$this->notify('Storage quota reached. Total storage (' . number_format($this->totalAllocatedStorage, 1) . ' GB) meets or exceeds your plan allocation (' . number_format($this->maxAllowedStorage, 1) . ' GB). Please upgrade your service or delete drives to allow this configuration.', 'error');
// Reattaching doesn't add new storage — unused drives are already counted in totalAllocatedStorage.
// Only block if total storage strictly exceeds the quota (which would indicate a data inconsistency).
if ($this->totalAllocatedStorage > $this->maxAllowedStorage && $this->maxAllowedStorage > 0) {
$this->notify('Storage quota exceeded. Total storage (' . number_format($this->totalAllocatedStorage, 1) . ' GB) exceeds your plan allocation (' . number_format($this->maxAllowedStorage, 1) . ' GB). Please upgrade your service or delete drives to allow this configuration.', 'error');
return;
}
@@ -1097,6 +1094,7 @@ class Storage extends Component
$config = $proxmox->request(
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config'
);
$configData = $config->json()['data'] ?? [];
// Find the next available slot on the target bus by checking actual Proxmox config
$maxIndex = $this->busTypes[$targetBus]['max'] ?? 4;
@@ -1105,7 +1103,7 @@ class Storage extends Component
for ($i = 0; $i < $maxIndex; $i++) {
$slotKey = $targetBus . $i;
// Check if this slot is occupied in the actual config
if (!isset($config['data'][$slotKey])) {
if (!isset($configData[$slotKey])) {
$targetIndex = $i;
break;
}
@@ -1118,7 +1116,7 @@ class Storage extends Component
$targetKey = $targetBus . $targetIndex;
$unusedValue = $config['data'][$this->reattachingDriveKey] ?? null;
$unusedValue = $configData[$this->reattachingDriveKey] ?? null;
if (!$unusedValue) {
$this->notify('Unused drive no longer exists. Please refresh the page.', 'error');
$this->loadStorageConfiguration(false);
@@ -1184,7 +1182,7 @@ class Storage extends Component
}
// Check VM status
if ($this->status && $this->status->status !== 'stopped') {
if (!$this->status || $this->status->status !== 'stopped') {
$this->notify('VM must be stopped to delete drives.', 'error');
return;
}
@@ -1217,7 +1215,7 @@ class Storage extends Component
}
// Check VM status
if ($this->status && $this->status->status !== 'stopped') {
if (!$this->status || $this->status->status !== 'stopped') {
$this->notify('VM must be stopped to change SCSI controller type.', 'error');
return;
}