Add all files
This commit is contained in:
752
extensions/Servers/Proxmox/Livewire/BootOrder.php
Normal file
752
extensions/Servers/Proxmox/Livewire/BootOrder.php
Normal file
@@ -0,0 +1,752 @@
|
||||
<?php
|
||||
|
||||
namespace Paymenter\Extensions\Servers\Proxmox\Livewire;
|
||||
|
||||
use Illuminate\Support\Str;
|
||||
use Paymenter\Extensions\Servers\Proxmox\Models\Server;
|
||||
use Paymenter\Extensions\Servers\Proxmox\Proxmox;
|
||||
|
||||
class BootOrder extends Component
|
||||
{
|
||||
public array $availableDevices = [];
|
||||
|
||||
public array $currentOrder = [];
|
||||
|
||||
public array $order = [];
|
||||
|
||||
public ?string $cdDrive = null; // Keep for backward compatibility, but prefer cdDrives
|
||||
|
||||
public array $cdDrives = [];
|
||||
|
||||
public array $isoOptions = [];
|
||||
|
||||
public ?string $selectedIso = null; // Keep for backward compatibility
|
||||
|
||||
public ?string $currentIso = null; // Keep for backward compatibility
|
||||
|
||||
public array $selectedIsos = [];
|
||||
|
||||
public array $currentIsos = [];
|
||||
|
||||
/**
|
||||
* Boot configuration retrieved from the server.
|
||||
*/
|
||||
protected array $rawConfig = [];
|
||||
|
||||
public function mount(Server $server): void
|
||||
{
|
||||
parent::mount($server);
|
||||
|
||||
$this->loadBootConfiguration(false);
|
||||
}
|
||||
|
||||
public function refreshBootConfiguration()
|
||||
{
|
||||
$this->loadBootConfiguration();
|
||||
$this->notify('Boot devices refreshed successfully.', 'success');
|
||||
}
|
||||
|
||||
public function resetOrder()
|
||||
{
|
||||
$this->order = $this->currentOrder;
|
||||
}
|
||||
|
||||
public function addDevice(string $device): void
|
||||
{
|
||||
if (!isset($this->availableDevices[$device])) {
|
||||
$this->notify('Device is no longer available.', 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($device, $this->order)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->order[] = $device;
|
||||
}
|
||||
|
||||
public function removeDevice(string $device): void
|
||||
{
|
||||
$this->order = array_values(array_filter(
|
||||
$this->order,
|
||||
fn ($item) => $item !== $device
|
||||
));
|
||||
}
|
||||
|
||||
public function moveDevice(string $device, string $direction): void
|
||||
{
|
||||
$index = array_search($device, $this->order);
|
||||
|
||||
if ($index === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($direction === 'up' && $index > 0) {
|
||||
[$this->order[$index - 1], $this->order[$index]] = [$this->order[$index], $this->order[$index - 1]];
|
||||
} elseif ($direction === 'down' && $index < count($this->order) - 1) {
|
||||
[$this->order[$index + 1], $this->order[$index]] = [$this->order[$index], $this->order[$index + 1]];
|
||||
}
|
||||
}
|
||||
|
||||
public function saveOrder()
|
||||
{
|
||||
try {
|
||||
$proxmox = new Proxmox;
|
||||
|
||||
// If order is empty, we need to delete the boot parameter or set it to empty
|
||||
if (empty($this->order)) {
|
||||
// Delete the boot order parameter to reset to default
|
||||
$proxmox->request(
|
||||
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config',
|
||||
'put',
|
||||
[
|
||||
'delete' => 'boot',
|
||||
]
|
||||
);
|
||||
$this->notify('Boot order reset to default. The new sequence will be used on the next power event.', 'success');
|
||||
} else {
|
||||
$proxmox->request(
|
||||
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config',
|
||||
'put',
|
||||
[
|
||||
'boot' => 'order=' . implode(';', $this->order),
|
||||
]
|
||||
);
|
||||
$this->notify('Boot order updated. The new sequence will be used on the next power event.', 'success');
|
||||
}
|
||||
|
||||
$this->currentOrder = $this->order;
|
||||
} catch (\Exception $e) {
|
||||
$this->notify('Failed to update boot order: ' . $e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
public function createCdDrive()
|
||||
{
|
||||
if (count($this->cdDrives) >= 2) {
|
||||
$this->notify('Maximum of 2 CD/DVD-ROM drives allowed.', 'info');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$proxmox = new Proxmox;
|
||||
$slot = null;
|
||||
$lastError = null;
|
||||
|
||||
// Try IDE slots first (ide0-ide4, up to 5 attempts)
|
||||
for ($i = 0; $i < 5; $i++) {
|
||||
$ideSlot = 'ide' . $i;
|
||||
|
||||
// Skip if this slot already exists as a CD-ROM drive
|
||||
if (in_array($ideSlot, $this->cdDrives, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if slot exists and is already configured (not empty)
|
||||
if (isset($this->rawConfig[$ideSlot]) && $this->rawConfig[$ideSlot] !== '') {
|
||||
// Check if it's a CD-ROM drive
|
||||
if (is_string($this->rawConfig[$ideSlot]) && str_contains($this->rawConfig[$ideSlot], 'media=cdrom')) {
|
||||
// Already a CD-ROM, skip
|
||||
continue;
|
||||
}
|
||||
// Slot exists but is occupied by another device type, skip
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to create the drive on this slot
|
||||
try {
|
||||
$proxmox->request(
|
||||
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config',
|
||||
'put',
|
||||
[
|
||||
$ideSlot => 'none,media=cdrom',
|
||||
]
|
||||
);
|
||||
|
||||
$slot = $ideSlot;
|
||||
break; // Success, exit loop
|
||||
} catch (\Exception $e) {
|
||||
// Slot creation failed, try next slot
|
||||
$lastError = $e->getMessage();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// If no IDE slot worked, try SATA slots (sata0-sata5)
|
||||
if (!$slot) {
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$sataSlot = 'sata' . $i;
|
||||
|
||||
// Skip if this slot already exists as a CD-ROM drive
|
||||
if (in_array($sataSlot, $this->cdDrives, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if slot exists and is already configured
|
||||
if (isset($this->rawConfig[$sataSlot]) && $this->rawConfig[$sataSlot] !== '') {
|
||||
if (is_string($this->rawConfig[$sataSlot]) && str_contains($this->rawConfig[$sataSlot], 'media=cdrom')) {
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try to create the drive on this slot
|
||||
try {
|
||||
$proxmox->request(
|
||||
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config',
|
||||
'put',
|
||||
[
|
||||
$sataSlot => 'none,media=cdrom',
|
||||
]
|
||||
);
|
||||
|
||||
$slot = $sataSlot;
|
||||
break; // Success, exit loop
|
||||
} catch (\Exception $e) {
|
||||
// Slot creation failed, try next slot
|
||||
$lastError = $e->getMessage();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($slot) {
|
||||
$this->notify('CD/DVD-ROM drive created on ' . strtoupper($slot) . '.', 'success');
|
||||
$this->loadBootConfiguration(false);
|
||||
} else {
|
||||
$errorMessage = $lastError
|
||||
? 'Failed to create CD/DVD-ROM drive: ' . $lastError
|
||||
: 'No available IDE or SATA slots remain for a CD/DVD-ROM drive.';
|
||||
$this->notify($errorMessage, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
public function removeCdDrive(string $drive)
|
||||
{
|
||||
if (!in_array($drive, $this->cdDrives, true)) {
|
||||
$this->notify('Drive not found.', 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$proxmox = new Proxmox;
|
||||
$proxmox->request(
|
||||
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config',
|
||||
'put',
|
||||
[
|
||||
'delete' => $drive,
|
||||
]
|
||||
);
|
||||
|
||||
// Remove the drive from boot order if it exists
|
||||
if (in_array($drive, $this->order, true)) {
|
||||
$this->order = array_values(array_filter(
|
||||
$this->order,
|
||||
fn ($item) => $item !== $drive
|
||||
));
|
||||
// Also update currentOrder to reflect the change
|
||||
$this->currentOrder = array_values(array_filter(
|
||||
$this->currentOrder,
|
||||
fn ($item) => $item !== $drive
|
||||
));
|
||||
}
|
||||
|
||||
$this->notify('CD/DVD-ROM drive removed from ' . strtoupper($drive) . '.', 'success');
|
||||
$this->loadBootConfiguration(false);
|
||||
} catch (\Exception $e) {
|
||||
$this->notify('Failed to remove CD/DVD-ROM drive: ' . $e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
public function applyIso(?string $drive = null)
|
||||
{
|
||||
$targetDrive = $drive ?? $this->cdDrive;
|
||||
|
||||
if (!$targetDrive || !in_array($targetDrive, $this->cdDrives, true)) {
|
||||
$this->notify('Create a CD/DVD-ROM drive first.', 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$selectedIso = $drive
|
||||
? ($this->selectedIsos[$drive] ?? 'none')
|
||||
: ($this->selectedIso ?: 'none');
|
||||
|
||||
$iso = $selectedIso ?: 'none';
|
||||
$value = $iso === 'none' ? 'none,media=cdrom' : $iso . ',media=cdrom';
|
||||
|
||||
try {
|
||||
$proxmox = new Proxmox;
|
||||
$proxmox->request(
|
||||
'/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config',
|
||||
'put',
|
||||
[
|
||||
$targetDrive => $value,
|
||||
]
|
||||
);
|
||||
|
||||
$message = $iso === 'none'
|
||||
? 'ISO detached from CD/DVD-ROM drive on ' . strtoupper($targetDrive) . '.'
|
||||
: ('ISO attached to ' . strtoupper($targetDrive) . ': ' . $this->formatIsoLabel($iso));
|
||||
|
||||
$this->notify($message, 'success');
|
||||
|
||||
if ($drive) {
|
||||
$this->currentIsos[$drive] = $iso;
|
||||
} else {
|
||||
$this->currentIso = $iso;
|
||||
}
|
||||
|
||||
$this->loadBootConfiguration(false);
|
||||
} catch (\Exception $e) {
|
||||
$this->notify('Failed to update ISO selection: ' . $e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
public function ejectIso(?string $drive = null): void
|
||||
{
|
||||
$targetDrive = $drive ?? $this->cdDrive;
|
||||
|
||||
if (!$targetDrive || !in_array($targetDrive, $this->cdDrives, true)) {
|
||||
$this->notify('Create a CD/DVD-ROM drive first.', 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($drive) {
|
||||
$this->selectedIsos[$drive] = 'none';
|
||||
} else {
|
||||
$this->selectedIso = 'none';
|
||||
}
|
||||
|
||||
$this->applyIso($targetDrive);
|
||||
}
|
||||
|
||||
public function getHasChangesProperty(): bool
|
||||
{
|
||||
return $this->order !== $this->currentOrder;
|
||||
}
|
||||
|
||||
protected function loadBootConfiguration(bool $force = true): void
|
||||
{
|
||||
try {
|
||||
$proxmox = new Proxmox;
|
||||
$config = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data'];
|
||||
|
||||
$this->rawConfig = $config;
|
||||
$this->availableDevices = $this->extractBootableDevices($config);
|
||||
|
||||
$order = $this->parseBootOrder($config);
|
||||
$order = array_values(array_filter(
|
||||
$order,
|
||||
fn ($device) => !$this->shouldHideDevice($device, $config)
|
||||
));
|
||||
|
||||
if ($force || empty($this->order)) {
|
||||
$this->order = $order;
|
||||
}
|
||||
$this->currentOrder = $order;
|
||||
|
||||
$this->cdDrives = $this->detectCdromDevices($config);
|
||||
|
||||
// For backward compatibility, set first drive as cdDrive
|
||||
$this->cdDrive = !empty($this->cdDrives) ? $this->cdDrives[0] : null;
|
||||
|
||||
// Load ISO options if we have any drives
|
||||
if (!empty($this->cdDrives)) {
|
||||
$this->loadIsoOptionsList();
|
||||
|
||||
// Initialize selected and current ISOs for each drive
|
||||
$this->selectedIsos = [];
|
||||
$this->currentIsos = [];
|
||||
|
||||
foreach ($this->cdDrives as $drive) {
|
||||
$iso = $this->extractIsoFromConfig($config[$drive] ?? null);
|
||||
$this->selectedIsos[$drive] = $iso;
|
||||
$this->currentIsos[$drive] = $iso;
|
||||
}
|
||||
|
||||
// For backward compatibility
|
||||
if ($this->cdDrive) {
|
||||
$this->selectedIso = $this->selectedIsos[$this->cdDrive] ?? 'none';
|
||||
$this->currentIso = $this->currentIsos[$this->cdDrive] ?? 'none';
|
||||
}
|
||||
} else {
|
||||
$this->selectedIso = null;
|
||||
$this->currentIso = null;
|
||||
$this->selectedIsos = [];
|
||||
$this->currentIsos = [];
|
||||
$this->isoOptions = [];
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
$this->availableDevices = [];
|
||||
$this->order = [];
|
||||
$this->currentOrder = [];
|
||||
$this->cdDrive = null;
|
||||
$this->cdDrives = [];
|
||||
$this->isoOptions = [];
|
||||
$this->selectedIso = null;
|
||||
$this->currentIso = null;
|
||||
$this->selectedIsos = [];
|
||||
$this->currentIsos = [];
|
||||
$this->notify('Unable to load boot information: ' . $e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a normalized list of bootable devices from the VM config.
|
||||
*/
|
||||
protected function extractBootableDevices(array $config): array
|
||||
{
|
||||
$devices = [];
|
||||
$pattern = '/^(ide|sata|scsi|virtio|net|efidisk|usb|nvme|tpmstate|sd)\d+$/';
|
||||
|
||||
foreach ($config as $key => $value) {
|
||||
if (!is_string($key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (preg_match($pattern, $key)) {
|
||||
if ($this->shouldHideDevice($key, $config, $value)) {
|
||||
continue;
|
||||
}
|
||||
$devices[$key] = [
|
||||
'label' => $this->formatDeviceLabel($key, $value),
|
||||
'details' => $this->formatDeviceDetails($value),
|
||||
'raw' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure currently configured boot devices remain visible even if missing.
|
||||
foreach ($this->parseBootOrder($config) as $device) {
|
||||
if ($this->shouldHideDevice($device, $config)) {
|
||||
continue;
|
||||
}
|
||||
if (!isset($devices[$device])) {
|
||||
$devices[$device] = [
|
||||
'label' => strtoupper($device),
|
||||
'details' => 'Device referenced in boot order but not present in config.',
|
||||
'raw' => null,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
ksort($devices);
|
||||
|
||||
return $devices;
|
||||
}
|
||||
|
||||
protected function formatDeviceLabel(string $device, mixed $value): string
|
||||
{
|
||||
$type = preg_replace('/\d+$/', '', $device);
|
||||
|
||||
$map = [
|
||||
'virtio' => 'VirtIO Disk',
|
||||
'scsi' => 'SCSI Disk',
|
||||
'sata' => 'SATA Disk',
|
||||
'ide' => 'IDE Device',
|
||||
'net' => 'Network Adapter',
|
||||
'efidisk' => 'EFI Disk',
|
||||
'usb' => 'USB Device',
|
||||
'nvme' => 'NVMe Disk',
|
||||
'tpmstate' => 'TPM State',
|
||||
'sd' => 'Secure Digital',
|
||||
];
|
||||
|
||||
$label = ($map[$type] ?? Str::upper($type)) . ' (' . Str::upper($device) . ')';
|
||||
|
||||
if (is_string($value) && str_contains($value, 'media=cdrom')) {
|
||||
$label .= ' • CD/DVD';
|
||||
}
|
||||
|
||||
return $label;
|
||||
}
|
||||
|
||||
protected function formatDeviceDetails(mixed $value): ?string
|
||||
{
|
||||
if (!is_string($value) || trim($value) === '') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$segments = array_map('trim', explode(',', $value));
|
||||
$path = array_shift($segments);
|
||||
$details = [];
|
||||
|
||||
foreach ($segments as $segment) {
|
||||
if (str_contains($segment, '=')) {
|
||||
[$key, $val] = array_map('trim', explode('=', $segment, 2));
|
||||
$details[] = ucfirst($key) . ': ' . $val;
|
||||
} elseif ($segment !== '') {
|
||||
$details[] = $segment;
|
||||
}
|
||||
}
|
||||
|
||||
$detailString = implode(', ', $details);
|
||||
|
||||
if ($path && $detailString) {
|
||||
return $path . ' • ' . $detailString;
|
||||
}
|
||||
|
||||
return $path ?: ($detailString ?: null);
|
||||
}
|
||||
|
||||
protected function parseBootOrder(array $config): array
|
||||
{
|
||||
$boot = $config['boot'] ?? '';
|
||||
$order = [];
|
||||
|
||||
if (is_string($boot) && str_contains($boot, 'order=')) {
|
||||
[, $list] = explode('order=', $boot, 2);
|
||||
$order = array_filter(array_map('trim', explode(';', $list)));
|
||||
} elseif (is_string($boot) && preg_match('/^[cdn]{1,3}$/i', $boot)) {
|
||||
$order = $this->mapLegacyOrder($boot, $config);
|
||||
}
|
||||
|
||||
if (empty($order) && isset($config['bootdisk'])) {
|
||||
$order[] = $config['bootdisk'];
|
||||
}
|
||||
|
||||
return array_values(array_filter($order));
|
||||
}
|
||||
|
||||
protected function mapLegacyOrder(string $legacy, array $config): array
|
||||
{
|
||||
$legacy = strtolower($legacy);
|
||||
$order = [];
|
||||
|
||||
$disk = $config['bootdisk'] ?? $this->detectDiskDevice($config);
|
||||
$cdrom = $this->detectCdromDevice($config);
|
||||
$network = $this->detectNetworkDevice($config);
|
||||
|
||||
foreach (str_split($legacy) as $flag) {
|
||||
if ($flag === 'c' && $disk) {
|
||||
$order[] = $disk;
|
||||
} elseif ($flag === 'd' && $cdrom) {
|
||||
$order[] = $cdrom;
|
||||
} elseif ($flag === 'n' && $network) {
|
||||
$order[] = $network;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique(array_filter($order)));
|
||||
}
|
||||
|
||||
protected function shouldHideDevice(string $device, array $config, mixed $value = null): bool
|
||||
{
|
||||
$value ??= $config[$device] ?? null;
|
||||
$deviceLower = strtolower($device);
|
||||
if (is_array($value)) {
|
||||
$valueLower = strtolower(json_encode($value));
|
||||
} elseif (is_string($value)) {
|
||||
$valueLower = strtolower($value);
|
||||
} else {
|
||||
$valueLower = strtolower((string) $value);
|
||||
}
|
||||
|
||||
if (
|
||||
str_contains($deviceLower, 'cloudinit')
|
||||
|| str_contains($valueLower, 'cloudinit')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
str_starts_with($deviceLower, 'tpmstate')
|
||||
|| str_contains($valueLower, 'tpm')
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (str_starts_with($deviceLower, 'efidisk')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected function detectDiskDevice(array $config): ?string
|
||||
{
|
||||
foreach ($config as $key => $value) {
|
||||
if (preg_match('/^(virtio|scsi|sata|ide|nvme|sd)\d+$/', $key)) {
|
||||
if (is_string($value) && str_contains($value, 'media=cdrom')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function detectCdromDevice(array $config): ?string
|
||||
{
|
||||
$drives = $this->detectCdromDevices($config);
|
||||
return !empty($drives) ? $drives[0] : null;
|
||||
}
|
||||
|
||||
protected function detectCdromDevices(array $config): array
|
||||
{
|
||||
$drives = [];
|
||||
|
||||
foreach ($config as $key => $value) {
|
||||
if (!preg_match('/^(ide|sata)\d+$/', $key) || !is_string($value)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!str_contains($value, 'media=cdrom')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (str_contains(strtolower($value), 'cloudinit')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$drives[] = $key;
|
||||
}
|
||||
|
||||
// Sort drives to maintain consistent order (ide0, ide1, sata0, sata1, etc.)
|
||||
usort($drives, function($a, $b) {
|
||||
// Extract type and number for comparison
|
||||
preg_match('/^(ide|sata)(\d+)$/', $a, $matchA);
|
||||
preg_match('/^(ide|sata)(\d+)$/', $b, $matchB);
|
||||
|
||||
// Sort by type first (ide before sata), then by number
|
||||
if ($matchA[1] !== $matchB[1]) {
|
||||
return strcmp($matchA[1], $matchB[1]);
|
||||
}
|
||||
|
||||
return (int)$matchA[2] <=> (int)$matchB[2];
|
||||
});
|
||||
|
||||
return $drives;
|
||||
}
|
||||
|
||||
protected function detectNetworkDevice(array $config): ?string
|
||||
{
|
||||
foreach ($config as $key => $value) {
|
||||
if (preg_match('/^net\d+$/', $key)) {
|
||||
return $key;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function findNextIdeSlot(array $config): ?string
|
||||
{
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$slot = 'ide' . $i;
|
||||
if (!array_key_exists($slot, $config)) {
|
||||
return $slot;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function findNextCdromSlot(array $config): ?string
|
||||
{
|
||||
// First, try to find an available IDE slot (ide0-ide3)
|
||||
for ($i = 0; $i < 4; $i++) {
|
||||
$slot = 'ide' . $i;
|
||||
if (!array_key_exists($slot, $config)) {
|
||||
return $slot;
|
||||
}
|
||||
}
|
||||
|
||||
// If all IDE slots are taken, try SATA slots (sata0-sata5)
|
||||
for ($i = 0; $i < 6; $i++) {
|
||||
$slot = 'sata' . $i;
|
||||
if (!array_key_exists($slot, $config)) {
|
||||
return $slot;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function loadIsoOptionsList(): void
|
||||
{
|
||||
try {
|
||||
$proxmox = new Proxmox;
|
||||
$response = $proxmox->request(
|
||||
'/nodes/' . $this->server->node->name . '/storage/cephfs/content',
|
||||
data: [
|
||||
'content' => 'iso',
|
||||
]
|
||||
);
|
||||
|
||||
$items = $response->json()['data'] ?? [];
|
||||
|
||||
$options = array_map(function ($item) {
|
||||
$volid = $item['volid'] ?? '';
|
||||
|
||||
return [
|
||||
'value' => $volid,
|
||||
'label' => $this->formatIsoLabel($volid),
|
||||
];
|
||||
}, $items);
|
||||
|
||||
usort($options, fn ($a, $b) => strcmp($a['label'], $b['label']));
|
||||
|
||||
array_unshift($options, [
|
||||
'value' => 'none',
|
||||
'label' => 'None',
|
||||
]);
|
||||
|
||||
$this->isoOptions = $options;
|
||||
} catch (\Exception $e) {
|
||||
$this->isoOptions = [
|
||||
[
|
||||
'value' => 'none',
|
||||
'label' => 'None',
|
||||
],
|
||||
];
|
||||
$this->notify('Unable to fetch ISO images from cephfs: ' . $e->getMessage(), 'error');
|
||||
}
|
||||
}
|
||||
|
||||
protected function formatIsoLabel(?string $volid): string
|
||||
{
|
||||
if (!$volid || $volid === 'none') {
|
||||
return 'None';
|
||||
}
|
||||
|
||||
if (str_contains($volid, ':')) {
|
||||
[, $path] = explode(':', $volid, 2);
|
||||
$path = ltrim($path, '/');
|
||||
} else {
|
||||
$path = $volid;
|
||||
}
|
||||
|
||||
return Str::of($path)->after('iso/')->value();
|
||||
}
|
||||
|
||||
protected function extractIsoFromConfig(?string $value): string
|
||||
{
|
||||
if (!$value) {
|
||||
return 'none';
|
||||
}
|
||||
|
||||
$segments = array_map('trim', explode(',', $value));
|
||||
$path = array_shift($segments);
|
||||
|
||||
if (!$path || strtolower($path) === 'none') {
|
||||
return 'none';
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('proxmox::boot-order')->layoutData([
|
||||
'sidebar' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user