781 lines
24 KiB
PHP
781 lines
24 KiB
PHP
<?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()
|
|
{
|
|
// Validate boot order entries against available devices
|
|
$validDevices = array_keys($this->availableDevices);
|
|
$this->order = array_values(array_filter($this->order, fn ($device) => in_array($device, $validDevices, true)));
|
|
|
|
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;
|
|
|
|
// Fetch current VM config to check slot occupancy (rawConfig is protected and not persisted between requests)
|
|
try {
|
|
$currentConfig = $proxmox->request('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/config')->json()['data'] ?? [];
|
|
} catch (\Exception $e) {
|
|
$this->notify('Failed to fetch VM configuration: ' . $e->getMessage(), 'error');
|
|
return;
|
|
}
|
|
|
|
// Try IDE slots first (ide0-ide3, up to 4 valid IDE slots)
|
|
for ($i = 0; $i < 4; $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($currentConfig[$ideSlot]) && $currentConfig[$ideSlot] !== '') {
|
|
// Check if it's a CD-ROM drive
|
|
if (is_string($currentConfig[$ideSlot]) && str_contains($currentConfig[$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($currentConfig[$sataSlot]) && $currentConfig[$sataSlot] !== '') {
|
|
if (is_string($currentConfig[$sataSlot]) && str_contains($currentConfig[$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)
|
|
{
|
|
// Validate drive format: only ide0-3 or sata0-5 are valid CD/DVD-ROM slots
|
|
if (!preg_match('/^(ide|sata)\d+$/', $drive)) {
|
|
$this->notify('Invalid drive identifier.', 'error');
|
|
return;
|
|
}
|
|
|
|
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';
|
|
|
|
// Validate the selected ISO against available options to prevent arbitrary paths
|
|
if ($iso !== 'none') {
|
|
$validIsos = collect($this->isoOptions)->pluck('value')->toArray();
|
|
if (!in_array($iso, $validIsos, true)) {
|
|
$this->notify('Invalid ISO selection.', 'error');
|
|
return;
|
|
}
|
|
}
|
|
|
|
$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;
|
|
}
|
|
|
|
// Always update selectedIsos for the target drive
|
|
$this->selectedIsos[$targetDrive] = 'none';
|
|
if (!$drive) {
|
|
$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,
|
|
]);
|
|
}
|
|
}
|
|
|