846 lines
59 KiB
PHP
846 lines
59 KiB
PHP
<x-proxmox::layout :server="$server" view="boot-order" :status="$status" :status-polling="true" :status-polling-interval="'2s'">
|
|
<style>
|
|
/* Hide sidebar in problematic resolution range (768px - 1023px) */
|
|
@media screen and (min-width: 768px) and (max-width: 1023px) {
|
|
/* Hide the sidebar - must override Tailwind's md:flex which applies display:flex at 768px+ */
|
|
/* Using high specificity selectors to ensure override */
|
|
html body > div.w-full.flex.flex-grow > aside.w-64.h-screen,
|
|
html body > div.w-full.flex > aside.w-64.h-screen,
|
|
html body aside.w-64.h-screen.mt-14,
|
|
html body aside.w-64.h-screen.mt-24,
|
|
body aside.w-64[class*="md:flex"] {
|
|
display: none !important;
|
|
visibility: hidden !important;
|
|
opacity: 0 !important;
|
|
pointer-events: none !important;
|
|
width: 0 !important;
|
|
max-width: 0 !important;
|
|
overflow: hidden !important;
|
|
position: absolute !important;
|
|
left: -9999px !important;
|
|
}
|
|
/* Remove left margin from main content container when sidebar is hidden */
|
|
html body > div.w-full.flex.flex-grow > div[class*="md:ml-64"],
|
|
html body > div.w-full.flex > div[class*="md:ml-64"],
|
|
body > div > div[class*="md:ml-64"],
|
|
div[class*="md:ml-64"] {
|
|
margin-left: 0 !important;
|
|
}
|
|
/* Remove right margin for RTL when sidebar is hidden */
|
|
html body > div.w-full.flex.flex-grow > div[class*="rtl:md:mr-64"],
|
|
html body > div.w-full.flex > div[class*="rtl:md:mr-64"],
|
|
body > div > div[class*="rtl:md:mr-64"],
|
|
div[class*="rtl:md:mr-64"] {
|
|
margin-right: 0 !important;
|
|
}
|
|
}
|
|
</style>
|
|
<div class="space-y-4 lg:space-y-6 px-2 sm:px-0 overflow-x-hidden">
|
|
<div class="bg-info/10 border border-info/30 rounded-lg p-3 lg:p-4"
|
|
x-data="{
|
|
isCollapsed: localStorage.getItem('boot-order-notice-collapsed') === 'true',
|
|
toggle() {
|
|
this.isCollapsed = !this.isCollapsed;
|
|
localStorage.setItem('boot-order-notice-collapsed', this.isCollapsed);
|
|
}
|
|
}">
|
|
<div class="flex items-start gap-3">
|
|
<x-ri-information-line class="size-5 text-info flex-shrink-0 mt-0.5" />
|
|
<div class="flex-1">
|
|
<div class="flex items-center justify-between mb-2">
|
|
<h3 class="text-sm font-semibold text-base">Cloud-init Configuration Notice</h3>
|
|
<button @click="toggle()" type="button" class="text-base/60 hover:text-base transition-colors">
|
|
<x-ri-arrow-down-s-line x-bind:class="isCollapsed ? 'size-4 transition-transform rotate-180' : 'size-4 transition-transform'" />
|
|
</button>
|
|
</div>
|
|
<div x-show="!isCollapsed" x-transition>
|
|
<p class="text-xs text-base/70 leading-relaxed">
|
|
<strong>Hostname, password, and networking settings</strong> are configured automatically via <strong>cloud-init</strong>.
|
|
This applies to standard operating system installations that support cloud-init. However, if you are:
|
|
</p>
|
|
<ul class="text-xs text-base/70 leading-relaxed mt-2 ml-4 list-disc space-y-1">
|
|
<li>Installing from an ISO image</li>
|
|
<li>Configuring the operating system manually during installation</li>
|
|
<li>Using Windows (which does not support cloud-init)</li>
|
|
</ul>
|
|
<p class="text-xs text-base/70 leading-relaxed mt-2">
|
|
You will need to <strong>manually configure networking, security settings, and hostname</strong> within the operating system, as cloud-init will not be available in these scenarios.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-2">
|
|
<div class="flex items-center gap-2 min-w-0">
|
|
<h2 class="text-lg sm:text-xl font-semibold break-words">Boot Order</h2>
|
|
<div class="relative" x-data="{ open: false }" x-cloak>
|
|
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors">
|
|
<x-ri-question-line class="size-4" />
|
|
</button>
|
|
<!-- Modal Backdrop -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
@click="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
|
|
style="display: none;">
|
|
</div>
|
|
<!-- Modal -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0 scale-95"
|
|
x-transition:enter-end="opacity-100 scale-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100 scale-100"
|
|
x-transition:leave-end="opacity-0 scale-95"
|
|
@click.away="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
|
style="display: none;">
|
|
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
|
|
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
|
|
<x-ri-close-line class="size-5" />
|
|
</button>
|
|
<div class="flex items-center gap-3 mb-4 pr-8">
|
|
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10">
|
|
<x-ri-settings-3-line class="size-5 text-primary" />
|
|
</div>
|
|
<h6 class="font-sans text-lg font-semibold leading-tight text-base">
|
|
Boot Order Configuration
|
|
</h6>
|
|
</div>
|
|
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
|
|
Determines the sequence in which the virtual machine attempts to boot from available devices. The system will try each device in the specified order until it finds a bootable medium.
|
|
</p>
|
|
<div class="pt-4 mt-4 border-t border-neutral/30">
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
|
|
<span class="font-semibold text-base/90">Note:</span> If no explicit boot order is configured, QEMU uses the default platform boot sequence. Changes take effect on the next power event (start, reboot, or reset). The boot order is populated directly from the QEMU configuration via the QEMU API.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p class="text-sm text-base/60">
|
|
Control the sequence used when attempting to boot this virtual machine. The list below is
|
|
populated directly from the QEMU configuration via the API.
|
|
</p>
|
|
<p class="text-xs text-base/50 break-words overflow-wrap-anywhere">
|
|
Currently applied:
|
|
@if (count($currentOrder))
|
|
<span class="inline-block">{{ implode(' → ', array_map(fn ($device) => $availableDevices[$device]['label'] ?? strtoupper($device), $currentOrder)) }}</span>
|
|
@else
|
|
Default platform order (no explicit devices configured)
|
|
@endif
|
|
</p>
|
|
</div>
|
|
|
|
@if(count($cdDrives) < 2)
|
|
<div class="bg-background-secondary border border-neutral rounded-lg p-3 lg:p-4 space-y-3 lg:space-y-4">
|
|
<div class="flex flex-wrap items-center justify-between gap-2 min-w-0">
|
|
<div class="flex-1">
|
|
<div class="flex items-center gap-2 min-w-0 flex-1">
|
|
<h3 class="text-base sm:text-lg font-semibold break-words">Create CD/DVD-ROM Drive</h3>
|
|
<div class="relative" x-data="{ open: false }" x-cloak>
|
|
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors">
|
|
<x-ri-question-line class="size-4" />
|
|
</button>
|
|
<!-- Modal Backdrop -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
@click="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
|
|
style="display: none;">
|
|
</div>
|
|
<!-- Modal -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0 scale-95"
|
|
x-transition:enter-end="opacity-100 scale-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100 scale-100"
|
|
x-transition:leave-end="opacity-0 scale-95"
|
|
@click.away="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
|
style="display: none;">
|
|
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
|
|
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
|
|
<x-ri-close-line class="size-5" />
|
|
</button>
|
|
<div class="flex items-center gap-3 mb-4 pr-8">
|
|
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10">
|
|
<x-ri-dvd-line class="size-5 text-primary" />
|
|
</div>
|
|
<h6 class="font-sans text-lg font-semibold leading-tight text-base">
|
|
Create CD/DVD-ROM Drive
|
|
</h6>
|
|
</div>
|
|
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
|
|
Adds a virtual optical drive to your VM using the next available IDE or SATA slot. The system automatically selects an unused slot (IDE0-IDE4 or SATA0-SATA5).
|
|
</p>
|
|
<div class="pt-4 mt-4 border-t border-neutral/30">
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75 mb-2">
|
|
<span class="font-semibold text-base/90">Limitations:</span> Cloud-init media devices are automatically ignored. Maximum of 2 CD/DVD-ROM drives per virtual machine.
|
|
</p>
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
|
|
Once created, the drive will appear in the boot order and can be used to mount ISO images for operating system installation or other purposes.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-base/60">
|
|
Use the next available IDE or SATA slot to add a virtual optical drive. Cloud-init media is ignored. Maximum of 2 drives allowed.
|
|
</p>
|
|
</div>
|
|
<span class="text-xs font-semibold {{ count($cdDrives) === 0 ? 'text-warning' : 'text-base/60' }}">
|
|
{{ count($cdDrives) === 0 ? 'No drive configured' : count($cdDrives) . ' of 2 drives' }}
|
|
</span>
|
|
</div>
|
|
|
|
<x-button.secondary wire:click="createCdDrive" wire:loading.attr="disabled"
|
|
class="w-full lg:w-48"
|
|
:disabled="count($cdDrives) >= 2">
|
|
<x-loading target="createCdDrive" />
|
|
Create CD/DVD-ROM Drive
|
|
</x-button.secondary>
|
|
</div>
|
|
@endif
|
|
|
|
@foreach($cdDrives as $index => $drive)
|
|
<div class="bg-background-secondary border border-neutral rounded-lg p-4 space-y-4" wire:key="cd-drive-{{ $drive }}">
|
|
<div class="flex flex-wrap items-center justify-between gap-2 min-w-0">
|
|
<div class="flex-1">
|
|
<div class="flex items-center gap-2 min-w-0 flex-1">
|
|
<h3 class="text-base sm:text-lg font-semibold break-words">Mount ISO Image - {{ strtoupper($drive) }}</h3>
|
|
<div class="relative" x-data="{ open: false }" x-cloak>
|
|
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors">
|
|
<x-ri-question-line class="size-4" />
|
|
</button>
|
|
<!-- Modal Backdrop -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
@click="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
|
|
style="display: none;">
|
|
</div>
|
|
<!-- Modal -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0 scale-95"
|
|
x-transition:enter-end="opacity-100 scale-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100 scale-100"
|
|
x-transition:leave-end="opacity-0 scale-95"
|
|
@click.away="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
|
style="display: none;">
|
|
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
|
|
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
|
|
<x-ri-close-line class="size-5" />
|
|
</button>
|
|
<div class="flex items-center gap-3 mb-4 pr-8">
|
|
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10">
|
|
<x-ri-file-upload-line class="size-5 text-primary" />
|
|
</div>
|
|
<h6 class="font-sans text-lg font-semibold leading-tight text-base">
|
|
Mount ISO Image - {{ strtoupper($drive) }}
|
|
</h6>
|
|
</div>
|
|
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
|
|
Attach or detach ISO files from cephfs storage to the virtual CD/DVD-ROM drive. Select an ISO from the dropdown to mount it, or choose "None" to leave the drive empty.
|
|
</p>
|
|
<div class="pt-4 mt-4 border-t border-neutral/30 space-y-2.5">
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
|
|
<span class="font-semibold text-base/90">Apply:</span> Commits your selection and immediately mounts the ISO to the drive.
|
|
</p>
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
|
|
<span class="font-semibold text-base/90">Eject:</span> Quickly removes the currently mounted ISO, setting the drive to empty.
|
|
</p>
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
|
|
ISO images are stored in cephfs storage and can be used for operating system installation, recovery, or running live environments. The storage prefix is hidden in the dropdown for clarity.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-base/60">
|
|
Attach or detach an ISO from cephfs storage for the drive on {{ strtoupper($drive) }}.
|
|
</p>
|
|
</div>
|
|
<div class="flex items-center gap-2">
|
|
<button wire:click="removeCdDrive('{{ $drive }}')"
|
|
wire:loading.attr="disabled"
|
|
class="text-xs font-semibold text-error hover:text-error/80">
|
|
<x-loading target="removeCdDrive('{{ $drive }}')" />
|
|
<span wire:loading.remove wire:target="removeCdDrive('{{ $drive }}')">Remove</span>
|
|
<span wire:loading wire:target="removeCdDrive('{{ $drive }}')">Removing...</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-2 sm:flex-row sm:items-center min-w-0">
|
|
<div class="flex-1 min-w-0 relative w-full sm:w-auto">
|
|
<select id="iso-selection-{{ $drive }}"
|
|
wire:model.live="selectedIsos.{{ $drive }}"
|
|
class="w-full bg-background border border-neutral rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-secondary">
|
|
@foreach($isoOptions as $option)
|
|
<option value="{{ $option['value'] }}">{{ $option['label'] }}</option>
|
|
@endforeach
|
|
</select>
|
|
<div class="absolute right-2 top-1/2 -translate-y-1/2 pointer-events-none">
|
|
<div class="relative" x-data="{ open: false }" x-cloak>
|
|
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors pointer-events-auto">
|
|
<x-ri-question-line class="size-3.5" />
|
|
</button>
|
|
<!-- Modal Backdrop -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
@click="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
|
|
style="display: none;">
|
|
</div>
|
|
<!-- Modal -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0 scale-95"
|
|
x-transition:enter-end="opacity-100 scale-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100 scale-100"
|
|
x-transition:leave-end="opacity-0 scale-95"
|
|
@click.away="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
|
style="display: none;">
|
|
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
|
|
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
|
|
<x-ri-close-line class="size-5" />
|
|
</button>
|
|
<div class="flex items-center gap-3 mb-4 pr-8">
|
|
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10">
|
|
<x-ri-file-list-3-line class="size-5 text-primary" />
|
|
</div>
|
|
<h6 class="font-sans text-lg font-semibold leading-tight text-base">
|
|
ISO Selection
|
|
</h6>
|
|
</div>
|
|
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
|
|
Displays all available ISO images from cephfs storage. Select an ISO to mount it to the drive, or choose "None" to leave the drive empty.
|
|
</p>
|
|
<div class="pt-4 mt-4 border-t border-neutral/30">
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75 mb-2">
|
|
The dropdown shows only the filename (storage prefix is hidden for clarity). Changes to the selection are tracked in real-time, but you must click <span class="font-semibold text-base/90">Apply</span> to actually mount the ISO.
|
|
</p>
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
|
|
The Apply button will be disabled if the selected ISO matches what's currently mounted.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex w-full sm:w-auto gap-2 min-w-0">
|
|
<x-button.primary wire:click="applyIso('{{ $drive }}')"
|
|
wire:loading.attr="disabled"
|
|
class="flex-1 sm:flex-none"
|
|
:disabled="($currentIsos[$drive] ?? 'none') === ($selectedIsos[$drive] ?? 'none')">
|
|
<x-loading target="applyIso('{{ $drive }}')" />
|
|
<span wire:loading.remove wire:target="applyIso('{{ $drive }}')">Apply</span>
|
|
<span wire:loading wire:target="applyIso('{{ $drive }}')">Applying...</span>
|
|
</x-button.primary>
|
|
<x-button.danger wire:click="ejectIso('{{ $drive }}')"
|
|
wire:loading.attr="disabled"
|
|
class="flex-1 sm:flex-none"
|
|
:disabled="!isset($currentIsos[$drive]) || $currentIsos[$drive] === 'none'">
|
|
<x-loading target="ejectIso('{{ $drive }}')" />
|
|
<span wire:loading.remove wire:target="ejectIso('{{ $drive }}')">Eject</span>
|
|
<span wire:loading wire:target="ejectIso('{{ $drive }}')">Ejecting...</span>
|
|
</x-button.danger>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-base/50">
|
|
Select "None" to leave the drive empty. Storage prefix is hidden for clarity.
|
|
</p>
|
|
|
|
@if(isset($currentIsos[$drive]) && $currentIsos[$drive] !== 'none')
|
|
<div class="text-xs text-base/60">
|
|
Currently mounted: <strong>{{ $this->formatIsoLabel($currentIsos[$drive]) }}</strong>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
|
|
@if(count($cdDrives) > 0)
|
|
<div class="bg-background-secondary border border-neutral rounded-lg p-3 lg:p-4 space-y-3 lg:space-y-4">
|
|
<div class="flex flex-wrap items-center justify-between gap-2 min-w-0">
|
|
<div class="flex-1">
|
|
<div class="flex items-center gap-2 min-w-0 flex-1">
|
|
<h3 class="text-base sm:text-lg font-semibold break-words">Request ISO</h3>
|
|
<div class="relative" x-data="{ open: false }" x-cloak>
|
|
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors">
|
|
<x-ri-question-line class="size-4" />
|
|
</button>
|
|
<!-- Modal Backdrop -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
@click="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
|
|
style="display: none;">
|
|
</div>
|
|
<!-- Modal -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0 scale-95"
|
|
x-transition:enter-end="opacity-100 scale-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100 scale-100"
|
|
x-transition:leave-end="opacity-0 scale-95"
|
|
@click.away="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
|
style="display: none;">
|
|
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
|
|
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
|
|
<x-ri-close-line class="size-5" />
|
|
</button>
|
|
<div class="flex items-center gap-3 mb-4 pr-8">
|
|
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10">
|
|
<x-ri-customer-service-2-line class="size-5 text-primary" />
|
|
</div>
|
|
<h6 class="font-sans text-lg font-semibold leading-tight text-base">
|
|
Request ISO Image
|
|
</h6>
|
|
</div>
|
|
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
|
|
Opens a support ticket form where you can request that a specific ISO image be added to the cephfs storage.
|
|
</p>
|
|
<div class="pt-4 mt-4 border-t border-neutral/30">
|
|
<p class="block font-sans text-xs font-semibold leading-relaxed text-base/90 mb-2.5">
|
|
When submitting a request, include:
|
|
</p>
|
|
<ul class="block font-sans text-xs font-normal leading-relaxed text-base/75 space-y-1.5 ml-4 list-disc mb-3">
|
|
<li>An active public URL of the image in <span class="font-semibold text-base/90">.iso format</span></li>
|
|
<li>A project page of the distribution</li>
|
|
<li>Name and version of the distribution</li>
|
|
</ul>
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
|
|
Submit your request under the <span class="font-semibold text-base/90">"ISO Request"</span> department for faster processing. Once added, the ISO will appear in the ISO selection dropdown for all users.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-base/60">
|
|
Submit a support ticket to request an ISO image be added to cephfs storage.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-col gap-3">
|
|
<x-button.primary
|
|
onclick="window.open('https://billing.virtnet.bond/tickets/create', '_blank')"
|
|
class="w-full sm:w-auto">
|
|
Request ISO
|
|
</x-button.primary>
|
|
|
|
<div class="bg-info/10 border border-info/30 rounded-lg p-2 sm:p-3">
|
|
<div class="flex items-start gap-2">
|
|
<x-ri-information-line class="size-4 text-info flex-shrink-0 mt-0.5" />
|
|
<div class="flex-1">
|
|
<p class="text-xs text-base/70 leading-relaxed">
|
|
When requesting an ISO, please ensure your ticket includes:
|
|
</p>
|
|
<ul class="text-xs text-base/70 leading-relaxed mt-1.5 ml-4 list-disc space-y-1">
|
|
<li>An active public URL of the image in <strong>.iso format</strong></li>
|
|
<li>A project page of the distribution</li>
|
|
<li>Name and version of the distribution</li>
|
|
</ul>
|
|
<p class="text-xs text-base/70 leading-relaxed mt-1.5">
|
|
Please submit your request under the <strong>"ISO Request"</strong> department.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<div class="grid grid-cols-1 xl:grid-cols-2 gap-4 lg:gap-6">
|
|
<div class="bg-background-secondary border border-neutral rounded-lg p-4 space-y-3">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex-1">
|
|
<div class="flex items-center gap-2 min-w-0 flex-1">
|
|
<h3 class="text-base sm:text-lg font-semibold break-words">Boot priority</h3>
|
|
<div class="relative" x-data="{ open: false }" x-cloak>
|
|
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors">
|
|
<x-ri-question-line class="size-4" />
|
|
</button>
|
|
<!-- Modal Backdrop -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
@click="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
|
|
style="display: none;">
|
|
</div>
|
|
<!-- Modal -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0 scale-95"
|
|
x-transition:enter-end="opacity-100 scale-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100 scale-100"
|
|
x-transition:leave-end="opacity-0 scale-95"
|
|
@click.away="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
|
style="display: none;">
|
|
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
|
|
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
|
|
<x-ri-close-line class="size-5" />
|
|
</button>
|
|
<div class="flex items-center gap-3 mb-4 pr-8">
|
|
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10">
|
|
<x-ri-list-ordered class="size-5 text-primary" />
|
|
</div>
|
|
<h6 class="font-sans text-lg font-semibold leading-tight text-base">
|
|
Boot Priority
|
|
</h6>
|
|
</div>
|
|
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
|
|
Shows the current boot sequence for your virtual machine. Devices are listed in order from highest to lowest priority. The VM will attempt to boot from each device in sequence until it finds a bootable medium.
|
|
</p>
|
|
<div class="pt-4 mt-4 border-t border-neutral/30 space-y-2.5">
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
|
|
<span class="font-semibold text-base/90">Move up/down:</span> Reorder devices to adjust boot priority.
|
|
</p>
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
|
|
<span class="font-semibold text-base/90">Remove:</span> Exclude a device from the boot sequence.
|
|
</p>
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
|
|
The "Synced" indicator shows when your changes match the saved configuration; "Unsaved changes" indicates modifications that need to be saved. Changes take effect on the next power event.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-base/60">Move devices up or down to adjust the order.</p>
|
|
</div>
|
|
<span class="text-xs font-medium {{ $this->hasChanges ? 'text-warning' : 'text-success' }}">
|
|
{{ $this->hasChanges ? 'Unsaved changes' : 'Synced' }}
|
|
</span>
|
|
</div>
|
|
|
|
<div class="space-y-2">
|
|
@forelse ($order as $index => $device)
|
|
<div wire:key="boot-order-{{ $device }}"
|
|
class="flex flex-col gap-2 border border-neutral/60 rounded-md bg-background px-2 sm:px-3 py-2 overflow-hidden">
|
|
<div class="flex items-start justify-between gap-2 sm:gap-3 min-w-0">
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-sm font-semibold break-words">
|
|
{{ $index + 1 }}. {{ $availableDevices[$device]['label'] ?? strtoupper($device) }}
|
|
</p>
|
|
@if ($availableDevices[$device]['details'] ?? false)
|
|
<p class="text-xs text-base/60 break-words mt-1">
|
|
{{ $availableDevices[$device]['details'] }}
|
|
</p>
|
|
@endif
|
|
</div>
|
|
<button wire:click="removeDevice('{{ $device }}')" type="button"
|
|
class="text-xs text-error hover:text-error/80 font-semibold">
|
|
Remove
|
|
</button>
|
|
</div>
|
|
|
|
<div class="flex flex-row gap-2">
|
|
<button wire:click="moveDevice('{{ $device }}', 'up')" type="button"
|
|
class="flex-1 border border-neutral rounded-md py-1 text-xs font-semibold {{ $index === 0 ? 'text-base/30 cursor-not-allowed opacity-50' : 'hover:bg-background-secondary' }}"
|
|
@disabled($index === 0)>
|
|
↑ Move up
|
|
</button>
|
|
<button wire:click="moveDevice('{{ $device }}', 'down')" type="button"
|
|
class="flex-1 border border-neutral rounded-md py-1 text-xs font-semibold {{ $index === count($order) - 1 ? 'text-base/30 cursor-not-allowed opacity-50' : 'hover:bg-background-secondary' }}"
|
|
@disabled($index === count($order) - 1)>
|
|
↓ Move down
|
|
</button>
|
|
</div>
|
|
</div>
|
|
@empty
|
|
<div class="border border-dashed border-neutral rounded-md px-4 py-6 text-center text-sm text-base/60">
|
|
Select devices from the list on the right to build a boot order.
|
|
</div>
|
|
@endforelse
|
|
</div>
|
|
</div>
|
|
|
|
@php
|
|
$remainingDevices = array_values(array_diff(array_keys($availableDevices), $order));
|
|
@endphp
|
|
|
|
<div class="bg-background-secondary border border-neutral rounded-lg p-4 space-y-3">
|
|
<div class="flex items-center justify-between">
|
|
<div class="flex-1">
|
|
<div class="flex items-center gap-2 min-w-0 flex-1">
|
|
<h3 class="text-base sm:text-lg font-semibold break-words">Available devices</h3>
|
|
<div class="relative" x-data="{ open: false }" x-cloak>
|
|
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors">
|
|
<x-ri-question-line class="size-4" />
|
|
</button>
|
|
<!-- Modal Backdrop -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
@click="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
|
|
style="display: none;">
|
|
</div>
|
|
<!-- Modal -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0 scale-95"
|
|
x-transition:enter-end="opacity-100 scale-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100 scale-100"
|
|
x-transition:leave-end="opacity-0 scale-95"
|
|
@click.away="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
|
style="display: none;">
|
|
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
|
|
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
|
|
<x-ri-close-line class="size-5" />
|
|
</button>
|
|
<div class="flex items-center gap-3 mb-4 pr-8">
|
|
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10">
|
|
<x-ri-hard-drive-2-line class="size-5 text-primary" />
|
|
</div>
|
|
<h6 class="font-sans text-lg font-semibold leading-tight text-base">
|
|
Available Devices
|
|
</h6>
|
|
</div>
|
|
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
|
|
Lists all bootable devices detected in your VM configuration that are not currently part of the boot sequence.
|
|
</p>
|
|
<div class="pt-4 mt-4 border-t border-neutral/30">
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75 mb-2">
|
|
<span class="font-semibold text-base/90">Device Types:</span> Disk drives (VirtIO, SCSI, SATA, IDE, NVMe), network adapters, and CD/DVD-ROM drives. Each device shows its type, identifier, and configuration details.
|
|
</p>
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
|
|
Click <span class="font-semibold text-base/90">Add</span> to include a device in the boot priority list. Devices are automatically detected from the QEMU configuration via the QEMU API.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<p class="text-xs text-base/60">These devices can be part of the boot sequence.</p>
|
|
</div>
|
|
<span class="text-xs text-base/60">{{ count($remainingDevices) }} available</span>
|
|
</div>
|
|
|
|
<div class="space-y-2 max-h-[32rem] overflow-y-auto pr-1 -mx-1 px-1">
|
|
@forelse ($remainingDevices as $device)
|
|
<div wire:key="boot-device-{{ $device }}"
|
|
class="flex flex-col gap-2 border border-neutral/60 rounded-md bg-background px-2 sm:px-3 py-2 overflow-hidden">
|
|
<div class="flex items-start justify-between gap-2 sm:gap-3 min-w-0">
|
|
<div class="flex-1 min-w-0">
|
|
<p class="text-sm font-semibold break-words">{{ $availableDevices[$device]['label'] ?? strtoupper($device) }}</p>
|
|
@if ($availableDevices[$device]['details'] ?? false)
|
|
<p class="text-xs text-base/60 break-words mt-1">{{ $availableDevices[$device]['details'] }}</p>
|
|
@endif
|
|
</div>
|
|
<button wire:click="addDevice('{{ $device }}')" type="button"
|
|
class="text-xs font-semibold text-primary hover:text-primary/80">
|
|
Add
|
|
</button>
|
|
</div>
|
|
</div>
|
|
@empty
|
|
<div class="border border-dashed border-neutral rounded-md px-4 py-6 text-center text-sm text-base/60">
|
|
All detected devices are already part of the boot priority list.
|
|
</div>
|
|
@endforelse
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="flex flex-col sm:flex-row items-stretch sm:items-center gap-2">
|
|
<div class="flex-1 w-full sm:w-auto">
|
|
<x-button.secondary wire:click="refreshBootConfiguration" wire:loading.attr="disabled"
|
|
class="w-full">
|
|
<x-loading target="refreshBootConfiguration" />
|
|
Refresh
|
|
</x-button.secondary>
|
|
</div>
|
|
<div class="flex items-center gap-2 flex-1 w-full sm:w-auto">
|
|
<x-button.secondary wire:click="resetOrder" :disabled="!$this->hasChanges"
|
|
class="flex-1">
|
|
Reset to current
|
|
</x-button.secondary>
|
|
<div class="relative" x-data="{ open: false }" x-cloak>
|
|
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors" :disabled="!$this->hasChanges">
|
|
<x-ri-question-line class="size-4" />
|
|
</button>
|
|
<!-- Modal Backdrop -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
@click="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
|
|
style="display: none;">
|
|
</div>
|
|
<!-- Modal -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0 scale-95"
|
|
x-transition:enter-end="opacity-100 scale-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100 scale-100"
|
|
x-transition:leave-end="opacity-0 scale-95"
|
|
@click.away="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
|
style="display: none;">
|
|
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
|
|
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
|
|
<x-ri-close-line class="size-5" />
|
|
</button>
|
|
<div class="flex items-center gap-3 mb-4 pr-8">
|
|
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10">
|
|
<x-ri-restart-line class="size-5 text-primary" />
|
|
</div>
|
|
<h6 class="font-sans text-lg font-semibold leading-tight text-base">
|
|
Reset to Current
|
|
</h6>
|
|
</div>
|
|
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
|
|
Discards any unsaved changes to the boot order and restores it to match the last saved configuration.
|
|
</p>
|
|
<div class="pt-4 mt-4 border-t border-neutral/30">
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
|
|
This button is only enabled when there are unsaved changes. Use this if you want to undo modifications without refreshing the entire configuration from the server.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-center gap-2 flex-1 w-full sm:w-auto">
|
|
<x-button.primary wire:click="saveOrder" wire:loading.attr="disabled" :disabled="!$this->hasChanges"
|
|
class="flex-1">
|
|
<x-loading target="saveOrder" />
|
|
Save boot order
|
|
</x-button.primary>
|
|
<div class="relative" x-data="{ open: false }" x-cloak>
|
|
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors" :disabled="!$this->hasChanges">
|
|
<x-ri-question-line class="size-4" />
|
|
</button>
|
|
<!-- Modal Backdrop -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0"
|
|
x-transition:enter-end="opacity-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100"
|
|
x-transition:leave-end="opacity-0"
|
|
@click="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
|
|
style="display: none;">
|
|
</div>
|
|
<!-- Modal -->
|
|
<div x-show="open"
|
|
x-transition:enter="transition ease-out duration-300"
|
|
x-transition:enter-start="opacity-0 scale-95"
|
|
x-transition:enter-end="opacity-100 scale-100"
|
|
x-transition:leave="transition ease-in duration-200"
|
|
x-transition:leave-start="opacity-100 scale-100"
|
|
x-transition:leave-end="opacity-0 scale-95"
|
|
@click.away="open = false"
|
|
@keydown.escape.window="open = false"
|
|
class="fixed inset-0 z-50 flex items-center justify-center p-4"
|
|
style="display: none;">
|
|
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
|
|
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
|
|
<x-ri-close-line class="size-5" />
|
|
</button>
|
|
<div class="flex items-center gap-3 mb-4 pr-8">
|
|
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10">
|
|
<x-ri-save-line class="size-5 text-primary" />
|
|
</div>
|
|
<h6 class="font-sans text-lg font-semibold leading-tight text-base">
|
|
Save Boot Order
|
|
</h6>
|
|
</div>
|
|
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
|
|
Commits your boot order changes to the QEMU server configuration. The new boot sequence will be applied to the virtual machine and will take effect on the next power event (start, reboot, or reset).
|
|
</p>
|
|
<div class="pt-4 mt-4 border-t border-neutral/30">
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75 mb-2">
|
|
If the boot order is empty, the system will reset to the default platform boot sequence.
|
|
</p>
|
|
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
|
|
This button is only enabled when there are unsaved changes.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
</div>
|
|
</x-proxmox::layout>
|
|
|