Refactor Proxmox views for improved styling and consistency

- Updated subdomain.blade.php to enhance button styles and modal transitions.
- Changed text colors for better readability and consistency across the UI.
- Refactored system.blade.php to unify button components and improve layout structure.
- Adjusted modal z-index values for consistent layering.
- Enhanced accessibility by ensuring proper text contrast and hover states.
This commit is contained in:
2025-12-18 09:40:58 -05:00
parent 5c5184aec8
commit 0ed29d782e
22 changed files with 6734 additions and 3813 deletions

View File

@@ -17,10 +17,8 @@
width: 100%;
}
</style>
<div class="space-y-6" x-data="{ pageLoaded: false }" x-init="setTimeout(() => pageLoaded = true, 50)">
<div class="bg-info/10 border border-info/30 rounded-lg p-4 transition-all duration-300"
:class="pageLoaded ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-2'"
style="transition: all 0.4s ease-out;"
<div class="space-y-6">
<div class="bg-info/10 border border-info/30 rounded-lg p-4"
x-data="{
isCollapsed: localStorage.getItem('cloud-init-notice-collapsed') === 'true',
toggle() {
@@ -33,12 +31,12 @@
<div class="flex-1 space-y-2">
<div class="flex items-center justify-between">
<h3 class="text-sm font-semibold text-base">Cloud-init Configuration Notice</h3>
<button @click="toggle()" type="button" class="text-gray-500 dark:text-gray-400 hover:text-base transition-colors">
<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-gray-600 dark:text-gray-400 leading-relaxed">
<p class="text-xs text-base/70 leading-relaxed">
Cloud-init controls the metadata injected into your VM on first boot and whenever you regenerate the cloud-init drive.
Use <strong>Apply</strong> to save and regenerate the ISO, <strong>Refresh</strong> to pull fresh values, and <strong>Reset</strong> to discard local edits.
Manual installs (ISO workflows, Windows, or images without cloud-init) still require in-guest configuration.
@@ -52,12 +50,12 @@
<div class="flex items-center gap-2 min-w-0">
<h2 class="text-xl font-semibold break-words">Cloud-init Settings</h2>
<div class="relative" x-data="{ open: false }" x-cloak>
<button type="button" @click="open = true" class="text-base/50 hover:text-gray-600 dark:text-gray-400 transition-colors">
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors">
<x-ri-question-line class="size-4" />
</button>
<div x-show="open" x-transition.opacity class="fixed inset-0 z-[60] flex items-center justify-center p-4">
<div x-show="open" x-transition.opacity class="fixed inset-0 z-50 flex items-center justify-center p-4">
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="open = false"></div>
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6"
<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"
x-on:keydown.escape.window="open = false">
<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" />
@@ -73,7 +71,7 @@
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
Configure cloud-init metadata that is injected into your virtual machine. These settings control user accounts, SSH keys, hostname, networking, and other first-boot configuration.
</p>
<div class="pt-4 mt-4 border-t border-gray-200 dark:border-gray-800 space-y-2.5">
<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> Saves your changes and regenerates the cloud-init ISO drive. Changes take effect on the next boot.
</p>
@@ -88,27 +86,25 @@
</div>
</div>
</div>
<p class="text-sm text-gray-500 dark:text-gray-400">
<p class="text-sm text-base/60">
These values map directly to the QEMU configuration (`ciuser`, `nameserver`, `ipconfigX`, etc.). Changes take effect on the next cloud-init run or boot.
</p>
</div>
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6">
<div class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-theme-sm p-4 space-y-4 transition-all duration-300 hover:shadow-md"
:class="pageLoaded ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4'"
style="transition: all 0.4s ease-out 0.15s;"
<div class="bg-background-secondary border border-neutral rounded-lg p-4 space-y-4"
x-data="{ showNetworkModal: @entangle('showNetworkModal') }">
<div class="flex items-center justify-between">
<div>
<div class="flex items-center gap-2 min-w-0">
<h3 class="text-lg font-semibold break-words">Access &amp; Credentials</h3>
<div class="relative" x-data="{ open: false }" x-cloak>
<button type="button" @click="open = true" class="text-base/50 hover:text-gray-600 dark:text-gray-400 transition-colors">
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors">
<x-ri-question-line class="size-4" />
</button>
<div x-show="open" x-transition.opacity class="fixed inset-0 z-[60] flex items-center justify-center p-4">
<div x-show="open" x-transition.opacity class="fixed inset-0 z-50 flex items-center justify-center p-4">
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="open = false"></div>
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6"
<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"
x-on:keydown.escape.window="open = false">
<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" />
@@ -124,7 +120,7 @@
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
Configure the default user account and SSH access for your virtual machine. These settings are used by cloud-init to create accounts and configure SSH keys on first boot.
</p>
<div class="pt-4 mt-4 border-t border-gray-200 dark:border-gray-800 space-y-2.5">
<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">Username:</span> The default user account created by cloud-init. This user typically has sudo privileges.
</p>
@@ -139,7 +135,7 @@
</div>
</div>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Defaults used by cloud-init to create accounts and authorize SSH.</p>
<p class="text-xs text-base/60 mt-1">Defaults used by cloud-init to create accounts and authorize SSH.</p>
</div>
@if($this->accessCredentialsSynced)
<span class="text-xs font-semibold text-success">Synced</span>
@@ -157,18 +153,18 @@
{{ $ciUser !== '' ? $ciUser : 'Template default (uses image-defined user)' }}
</span>
<button type="button"
class="text-gray-600 dark:text-gray-400 hover:text-gray-500 dark:text-gray-400 ml-2"
class="text-base/70 hover:text-base/60 ml-2"
@click="showCiUserModal = true"
aria-label="Edit Cloud-init user">
<x-ri-edit-fill class="inline size-4" />
</button>
</div>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400">Use distro-friendly names. Leave blank to keep the template default.</p>
<p class="text-xs text-base/60">Use distro-friendly names. Leave blank to keep the template default.</p>
<div x-cloak x-show="showCiUserModal" x-transition.opacity class="fixed inset-0 z-[60] flex items-center justify-center">
<div x-cloak x-show="showCiUserModal" x-transition.opacity class="fixed inset-0 z-50 flex items-center justify-center">
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="showCiUserModal = false"></div>
<div class="relative bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-theme-sm shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
<div class="relative bg-background-secondary border border-neutral rounded-lg shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
x-on:keydown.escape.window="showCiUserModal = false">
<div class="flex items-center justify-between">
<h4 class="text-base font-semibold text-base/90">Edit Cloud-init User</h4>
@@ -180,20 +176,20 @@
</button>
</div>
<div class="space-y-2">
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">Username</label>
<label class="text-xs font-semibold text-base/70">Username</label>
<input type="text"
wire:model.live="ciUser"
class="form-input w-full"
placeholder="e.g. ubuntu" />
<p class="text-xs text-gray-500 dark:text-gray-400">Leave blank to retain the template default user.</p>
<p class="text-xs text-base/60">Leave blank to retain the template default user.</p>
</div>
<div class="flex justify-end gap-2">
<x-proxmox::ui.button type="button" @click="showCiUserModal = false" variant="secondary">
<x-button.secondary type="button" @click="showCiUserModal = false">
Cancel
</x-proxmox::ui.button>
<x-proxmox::ui.button type="button" @click="showCiUserModal = false" variant="primary">
</x-button.secondary>
<x-button.primary type="button" @click="showCiUserModal = false">
Done
</x-proxmox::ui.button>
</x-button.primary>
</div>
</div>
</div>
@@ -212,26 +208,26 @@
</span>
@if(isset($server->password) && $server->password)
<button type="button"
class="text-gray-600 dark:text-gray-400 hover:text-gray-500 dark:text-gray-400 ml-2"
class="text-base/70 hover:text-base/60 ml-2"
@click="showPasswordViewModal = true"
aria-label="View Root Password">
<x-ri-eye-line class="inline size-4" />
</button>
@endif
<button type="button"
class="text-gray-600 dark:text-gray-400 hover:text-gray-500 dark:text-gray-400 ml-2"
class="text-base/70 hover:text-base/60 ml-2"
wire:click="startEditingPassword"
aria-label="Edit Root Password">
<x-ri-edit-fill class="inline size-4" />
</button>
</div>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400">Matches the Overview tab control. Use Save for database only, Save &amp; Apply to push via cloud-init.</p>
<p class="text-xs text-base/60">Matches the Overview tab control. Use Save for database only, Save &amp; Apply to push via cloud-init.</p>
@if(isset($server->password) && $server->password)
<div x-cloak x-show="showPasswordViewModal" x-transition.opacity class="fixed inset-0 z-[60] flex items-center justify-center">
<div x-cloak x-show="showPasswordViewModal" x-transition.opacity class="fixed inset-0 z-50 flex items-center justify-center">
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="showPasswordViewModal = false"></div>
<div class="relative bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-theme-sm shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
<div class="relative bg-background-secondary border border-neutral rounded-lg shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
x-on:keydown.escape.window="showPasswordViewModal = false"
x-data="{
copied: false,
@@ -266,9 +262,9 @@
</div>
@endif
<div x-cloak x-show="showPasswordEditModal" x-transition.opacity class="fixed inset-0 z-[60] flex items-center justify-center">
<div x-cloak x-show="showPasswordEditModal" x-transition.opacity class="fixed inset-0 z-50 flex items-center justify-center">
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="showPasswordEditModal = false; $wire.cancelEditingPassword()"></div>
<div class="relative bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-theme-sm shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
<div class="relative bg-background-secondary border border-neutral rounded-lg shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
x-on:keydown.escape.window="showPasswordEditModal = false; $wire.cancelEditingPassword()">
<div class="flex items-center justify-between">
<h4 class="text-base font-semibold text-base/90">Edit Root Password</h4>
@@ -281,14 +277,14 @@
</div>
<div class="space-y-3">
<div>
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">New Password</label>
<label class="text-xs font-semibold text-base/70">New Password</label>
<input type="password"
wire:model.defer="password"
class="form-input w-full mt-1"
placeholder="Enter new password" />
</div>
<div>
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">Confirm Password</label>
<label class="text-xs font-semibold text-base/70">Confirm Password</label>
<input type="password"
wire:model.defer="password_confirmation"
class="form-input w-full mt-1"
@@ -297,20 +293,20 @@
@if($passwordError)
<div class="text-sm text-error">{{ $passwordError }}</div>
@endif
<p class="text-xs text-gray-500 dark:text-gray-400">
<p class="text-xs text-base/60">
Save writes the password to the database. Save &amp; Apply also pushes it to the hypervisor; reboot for cloud-init to configure the guest OS.
</p>
</div>
<div class="flex justify-end gap-2">
<x-proxmox::ui.button type="button" @click="showPasswordEditModal = false; $wire.cancelEditingPassword()" variant="secondary">
<x-button.secondary type="button" @click="showPasswordEditModal = false; $wire.cancelEditingPassword()">
Cancel
</x-proxmox::ui.button>
<x-proxmox::ui.button type="button" wire:click="updatePassword" variant="primary">
</x-button.secondary>
<x-button.primary type="button" wire:click="updatePassword">
Save
</x-proxmox::ui.button>
<x-proxmox::ui.button type="button" wire:click="applyPassword" variant="primary">
</x-button.primary>
<x-button.primary type="button" wire:click="applyPassword">
Save &amp; Apply
</x-proxmox::ui.button>
</x-button.primary>
</div>
</div>
</div>
@@ -320,7 +316,7 @@
<label class="text-sm font-semibold text-base/80">SSH Public Keys</label>
<div class="space-y-2">
@forelse($sshKeys as $keyIndex => $key)
<div class="flex items-center gap-2 border border-neutral rounded-md bg-background px-3 py-2 text-xs text-gray-600 dark:text-gray-400 relative"
<div class="flex items-center gap-2 border border-neutral rounded-md bg-background px-3 py-2 text-xs text-base/70 relative"
x-data="{
showKeyModal: false,
copied: false,
@@ -333,7 +329,7 @@
}">
<span class="truncate flex-1" title="{{ $key }}">{{ $key }}</span>
<button type="button"
class="text-gray-600 dark:text-gray-400 hover:text-gray-500 dark:text-gray-400 ml-2"
class="text-base/70 hover:text-base/60 ml-2"
@click="showKeyModal = true"
aria-label="View SSH key">
<x-ri-eye-line class="inline size-4" />
@@ -344,9 +340,9 @@
<x-ri-close-line class="inline size-4" />
</button>
<div x-cloak x-show="showKeyModal" x-transition.opacity class="fixed inset-0 z-[60] flex items-center justify-center">
<div x-cloak x-show="showKeyModal" x-transition.opacity class="fixed inset-0 z-50 flex items-center justify-center">
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="showKeyModal = false"></div>
<div class="relative bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-theme-sm shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
<div class="relative bg-background-secondary border border-neutral rounded-lg shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
x-on:keydown.escape.window="showKeyModal = false">
<div class="flex items-center justify-between">
<h4 class="text-base font-semibold text-base/90">SSH Public Key</h4>
@@ -376,15 +372,15 @@
@endforelse
</div>
<div class="flex flex-col gap-2">
<x-proxmox::ui.button type="button" @click="showAddKeyModal = true" variant="secondary">
<x-button.secondary type="button" @click="showAddKeyModal = true">
Add Key
</x-proxmox::ui.button>
</x-button.secondary>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400">Add as many keys as needed. The platform management key is always kept but never shown.</p>
<p class="text-xs text-base/60">Add as many keys as needed. The platform management key is always kept but never shown.</p>
<div x-cloak x-show="showAddKeyModal" x-transition.opacity class="fixed inset-0 z-[60] flex items-center justify-center">
<div x-cloak x-show="showAddKeyModal" x-transition.opacity class="fixed inset-0 z-50 flex items-center justify-center">
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="showAddKeyModal = false"></div>
<div class="relative bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-theme-sm shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
<div class="relative bg-background-secondary border border-neutral rounded-lg shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
x-on:keydown.escape.window="showAddKeyModal = false">
<div class="flex items-center justify-between">
<h4 class="text-base font-semibold text-base/90">Add SSH Public Key</h4>
@@ -396,20 +392,20 @@
</button>
</div>
<div class="space-y-2">
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">SSH Key</label>
<label class="text-xs font-semibold text-base/70">SSH Key</label>
<textarea wire:model.defer="newSshKey"
class="form-input w-full h-28 resize-y"
placeholder="ssh-ed25519 AAAA... user@host"></textarea>
</div>
<div class="flex justify-end gap-2">
<x-proxmox::ui.button type="button" @click="showAddKeyModal = false" variant="secondary">
<x-button.secondary type="button" @click="showAddKeyModal = false">
Cancel
</x-proxmox::ui.button>
<x-proxmox::ui.button type="button"
</x-button.secondary>
<x-button.primary type="button"
wire:click="addSshKey"
@click="showAddKeyModal = false" variant="primary">
@click="showAddKeyModal = false">
Add Key
</x-proxmox::ui.button>
</x-button.primary>
</div>
</div>
</div>
@@ -419,24 +415,24 @@
<input id="ci-upgrade" type="checkbox" wire:model.live="upgradePackages" class="form-checkbox" />
<label for="ci-upgrade" class="text-sm font-semibold text-base/80">Upgrade packages on first boot</label>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400">
<p class="text-xs text-base/60">
Runs <code>apt/yum update &amp;&amp; upgrade</code> when cloud-init executes. Enable only if your template supports unattended upgrades.
</p>
</div>
</div>
<div class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-theme-sm p-4 space-y-4"
<div class="bg-background-secondary border border-neutral rounded-lg p-4 space-y-4"
x-data="{ showNetworkModal: @entangle('showNetworkModal') }">
<div class="flex items-center justify-between">
<div>
<div class="flex items-center gap-2 min-w-0">
<h3 class="text-lg font-semibold break-words">Network Configuration</h3>
<div class="relative" x-data="{ open: false }" x-cloak>
<button type="button" @click="open = true" class="text-base/50 hover:text-gray-600 dark:text-gray-400 transition-colors">
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors">
<x-ri-question-line class="size-4" />
</button>
<div x-show="open" x-transition.opacity class="fixed inset-0 z-[60] flex items-center justify-center p-4">
<div x-show="open" x-transition.opacity class="fixed inset-0 z-50 flex items-center justify-center p-4">
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="open = false"></div>
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6"
<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"
x-on:keydown.escape.window="open = false">
<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" />
@@ -452,7 +448,7 @@
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
Configure network interfaces for your virtual machine. Each network card corresponds to an <code>ipconfigX</code> parameter in the QEMU configuration, allowing you to set IPv4 and IPv6 addresses independently.
</p>
<div class="pt-4 mt-4 border-t border-gray-200 dark:border-gray-800 space-y-2.5">
<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">IP Configuration:</span> Each network interface can have separate IPv4 and IPv6 configurations. Use CIDR notation (e.g., 192.168.1.100/24) for IP addresses.
</p>
@@ -467,9 +463,9 @@
</div>
</div>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Each card corresponds to an <code>ipconfigX</code>. Configure IPv4 and IPv6 independently.</p>
<p class="text-xs text-base/60 mt-1">Each card corresponds to an <code>ipconfigX</code>. Configure IPv4 and IPv6 independently.</p>
</div>
<span class="text-xs text-gray-500 dark:text-gray-400">{{ count($networks) }} interfaces</span>
<span class="text-xs text-base/60">{{ count($networks) }} interfaces</span>
</div>
<div class="grid grid-cols-1 gap-4">
@@ -478,12 +474,12 @@
<div class="flex items-center justify-between">
<div>
<span class="text-sm font-semibold text-base/80 uppercase">{{ $network['key'] }}</span>
<p class="text-xs text-gray-500 dark:text-gray-400">
<p class="text-xs text-base/60">
Network Device: NET{{ str_replace('ipconfig', '', $network['key']) }}
</p>
</div>
<button wire:click="startEditingNetwork({{ $index }})" type="button"
class="text-gray-600 dark:text-gray-400 hover:text-gray-500 dark:text-gray-400 ml-2"
class="text-base/70 hover:text-base/60 ml-2"
@click="showNetworkModal = true">
<x-ri-edit-fill class="inline size-4" />
</button>
@@ -491,25 +487,25 @@
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">IPv4 Address (CIDR)</label>
<label class="text-xs font-semibold text-base/70">IPv4 Address (CIDR)</label>
<div class="text-sm text-base/80 mt-1">
{{ !empty($network['ip']) ? $network['ip'] : '—' }}
</div>
</div>
<div>
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">IPv4 Gateway</label>
<label class="text-xs font-semibold text-base/70">IPv4 Gateway</label>
<div class="text-sm text-base/80 mt-1">
{{ !empty($network['gw']) ? $network['gw'] : '—' }}
</div>
</div>
<div>
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">IPv6 Address (CIDR)</label>
<label class="text-xs font-semibold text-base/70">IPv6 Address (CIDR)</label>
<div class="text-sm text-base/80 mt-1">
{{ !empty($network['ip6']) ? $network['ip6'] : '—' }}
</div>
</div>
<div>
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">IPv6 Gateway</label>
<label class="text-xs font-semibold text-base/70">IPv6 Gateway</label>
<div class="text-sm text-base/80 mt-1">
{{ !empty($network['gw6']) ? $network['gw6'] : '—' }}
</div>
@@ -517,21 +513,21 @@
</div>
@if(($network['dhcp'] ?? false))
<div class="text-xs text-gray-500 dark:text-gray-400">
<div class="text-xs text-base/60">
<span class="font-semibold">DHCP:</span> Enabled
</div>
@endif
<p class="text-xs text-gray-500 dark:text-gray-400">
<p class="text-xs text-base/60">
Leave fields blank to remove static addressing. If both IPv4 fields are empty, cloud-init omits the IPv4 stanza.
</p>
</div>
@endforeach
</div>
<div x-cloak x-show="showNetworkModal" x-transition.opacity class="fixed inset-0 z-[60] flex items-center justify-center">
<div x-cloak x-show="showNetworkModal" x-transition.opacity class="fixed inset-0 z-50 flex items-center justify-center">
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="showNetworkModal = false; $wire.cancelEditingNetwork()"></div>
<div class="relative bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-theme-sm shadow-xl w-full max-w-2xl mx-4 p-5 space-y-4"
<div class="relative bg-background-secondary border border-neutral rounded-lg shadow-xl w-full max-w-2xl mx-4 p-5 space-y-4"
x-on:keydown.escape.window="showNetworkModal = false; $wire.cancelEditingNetwork()">
<div class="flex items-center justify-between">
<h4 class="text-base font-semibold text-base/90">Edit Network Configuration</h4>
@@ -546,36 +542,36 @@
<div class="space-y-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div>
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">IPv4 Address (CIDR)</label>
<label class="text-xs font-semibold text-base/70">IPv4 Address (CIDR)</label>
<input type="text" wire:model="editingNetwork.ip" class="form-input w-full mt-1"
placeholder="45.154.99.28/24" />
</div>
<div>
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">IPv4 Gateway</label>
<label class="text-xs font-semibold text-base/70">IPv4 Gateway</label>
<input type="text" wire:model="editingNetwork.gw" class="form-input w-full mt-1"
placeholder="45.154.99.17" />
</div>
<div>
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">IPv6 Address (CIDR)</label>
<label class="text-xs font-semibold text-base/70">IPv6 Address (CIDR)</label>
<input type="text" wire:model="editingNetwork.ip6" class="form-input w-full mt-1"
placeholder="2a0b:4e07::2:10/64" />
</div>
<div>
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">IPv6 Gateway</label>
<label class="text-xs font-semibold text-base/70">IPv6 Gateway</label>
<input type="text" wire:model="editingNetwork.gw6" class="form-input w-full mt-1"
placeholder="2a0b:4e07::1" />
</div>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400">
<p class="text-xs text-base/60">
Leave fields blank to remove static addressing. If both IPv4 fields are empty, cloud-init omits the IPv4 stanza.
</p>
<div class="flex justify-end gap-2">
<x-proxmox::ui.button wire:click="cancelEditingNetwork" @click="showNetworkModal = false" variant="secondary">
<x-button.secondary wire:click="cancelEditingNetwork" @click="showNetworkModal = false">
Cancel
</x-proxmox::ui.button>
<x-proxmox::ui.button wire:click="saveNetwork" @click="showNetworkModal = false" variant="primary">
</x-button.secondary>
<x-button.primary wire:click="saveNetwork" @click="showNetworkModal = false">
Save
</x-proxmox::ui.button>
</x-button.primary>
</div>
</div>
</div>
@@ -584,18 +580,18 @@
</div>
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6">
<div class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-theme-sm p-4 space-y-4">
<div class="bg-background-secondary border border-neutral rounded-lg p-4 space-y-4">
<div class="flex items-center justify-between">
<div>
<div class="flex items-center gap-2 min-w-0">
<h3 class="text-lg font-semibold break-words">DNS Settings</h3>
<div class="relative" x-data="{ open: false }" x-cloak>
<button type="button" @click="open = true" class="text-base/50 hover:text-gray-600 dark:text-gray-400 transition-colors">
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors">
<x-ri-question-line class="size-4" />
</button>
<div x-show="open" x-transition.opacity class="fixed inset-0 z-[60] flex items-center justify-center p-4">
<div x-show="open" x-transition.opacity class="fixed inset-0 z-50 flex items-center justify-center p-4">
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="open = false"></div>
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6"
<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"
x-on:keydown.escape.window="open = false">
<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" />
@@ -611,7 +607,7 @@
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
Configure DNS (Domain Name System) settings for your virtual machine. These settings control how the guest OS resolves domain names to IP addresses.
</p>
<div class="pt-4 mt-4 border-t border-gray-200 dark:border-gray-800 space-y-2.5">
<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">Search Domain:</span> The default domain suffix appended to hostnames that don't contain a domain. For example, with "example.com" as search domain, "server" resolves to "server.example.com".
</p>
@@ -626,7 +622,7 @@
</div>
</div>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Configures <code>searchdomain</code> and <code>nameserver</code> values.</p>
<p class="text-xs text-base/60 mt-1">Configures <code>searchdomain</code> and <code>nameserver</code> values.</p>
</div>
@if($this->dnsSettingsSynced)
<span class="text-xs font-semibold text-success">Synced</span>
@@ -644,18 +640,18 @@
{{ $dnsDomain !== '' ? $dnsDomain : 'Not configured (system default will be used)' }}
</span>
<button type="button"
class="text-gray-600 dark:text-gray-400 hover:text-gray-500 dark:text-gray-400 ml-2"
class="text-base/70 hover:text-base/60 ml-2"
@click="showDnsDomainModal = true"
aria-label="Edit DNS Domain">
<x-ri-edit-fill class="inline size-4" />
</button>
</div>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400">Appended to short hostnames for resolution.</p>
<p class="text-xs text-base/60">Appended to short hostnames for resolution.</p>
<div x-cloak x-show="showDnsDomainModal" x-transition.opacity class="fixed inset-0 z-[60] flex items-center justify-center">
<div x-cloak x-show="showDnsDomainModal" x-transition.opacity class="fixed inset-0 z-50 flex items-center justify-center">
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="showDnsDomainModal = false"></div>
<div class="relative bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-theme-sm shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
<div class="relative bg-background-secondary border border-neutral rounded-lg shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
x-on:keydown.escape.window="showDnsDomainModal = false">
<div class="flex items-center justify-between">
<h4 class="text-base font-semibold text-base/90">Edit DNS Domain</h4>
@@ -667,20 +663,20 @@
</button>
</div>
<div class="space-y-2">
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">DNS Domain (searchdomain)</label>
<label class="text-xs font-semibold text-base/70">DNS Domain (searchdomain)</label>
<input type="text"
wire:model.live="dnsDomain"
class="form-input w-full"
placeholder="example.internal" />
<p class="text-xs text-gray-500 dark:text-gray-400">Leave blank to use system default. Appended to short hostnames for resolution.</p>
<p class="text-xs text-base/60">Leave blank to use system default. Appended to short hostnames for resolution.</p>
</div>
<div class="flex justify-end gap-2">
<x-proxmox::ui.button type="button" @click="showDnsDomainModal = false" variant="secondary">
<x-button.secondary type="button" @click="showDnsDomainModal = false">
Cancel
</x-proxmox::ui.button>
<x-proxmox::ui.button type="button" @click="showDnsDomainModal = false" variant="primary">
</x-button.secondary>
<x-button.primary type="button" @click="showDnsDomainModal = false">
Done
</x-proxmox::ui.button>
</x-button.primary>
</div>
</div>
</div>
@@ -690,7 +686,7 @@
<label class="text-sm font-semibold text-base/80">DNS Nameservers</label>
<div class="space-y-2">
@forelse($dnsServers as $dnsIndex => $resolver)
<div class="flex items-center gap-2 border border-neutral rounded-md bg-background px-3 py-2 text-xs text-gray-600 dark:text-gray-400">
<div class="flex items-center gap-2 border border-neutral rounded-md bg-background px-3 py-2 text-xs text-base/70">
<span class="truncate flex-1" title="{{ $resolver }}">{{ $resolver }}</span>
<button type="button" wire:click="removeDnsServer({{ $dnsIndex }})"
class="text-error hover:text-error/80 ml-2">
@@ -702,15 +698,15 @@
@endforelse
</div>
<div class="flex flex-col gap-2">
<x-proxmox::ui.button type="button" @click="showAddDnsModal = true" variant="secondary">
<x-button.secondary type="button" @click="showAddDnsModal = true">
Add Nameserver
</x-proxmox::ui.button>
</x-button.secondary>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Entries are applied in order. Add one IPv4/IPv6 resolver per line.</p>
<p class="text-xs text-base/60 mt-1">Entries are applied in order. Add one IPv4/IPv6 resolver per line.</p>
<div x-cloak x-show="showAddDnsModal" x-transition.opacity class="fixed inset-0 z-[60] flex items-center justify-center">
<div x-cloak x-show="showAddDnsModal" x-transition.opacity class="fixed inset-0 z-50 flex items-center justify-center">
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="showAddDnsModal = false"></div>
<div class="relative bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-theme-sm shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
<div class="relative bg-background-secondary border border-neutral rounded-lg shadow-xl w-full max-w-xl mx-4 p-5 space-y-4"
x-on:keydown.escape.window="showAddDnsModal = false">
<div class="flex items-center justify-between">
<h4 class="text-base font-semibold text-base/90">Add DNS Nameserver</h4>
@@ -723,12 +719,12 @@
</div>
<div class="space-y-3">
<div class="space-y-2">
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">Quick Add</label>
<label class="text-xs font-semibold text-base/70">Quick Add</label>
<div class="grid grid-cols-2 gap-2">
<button type="button"
wire:click="quickAddDnsServers('cloudflare')"
@click="showAddDnsModal = false"
class="flex items-center gap-2 px-3 py-2 bg-background border border-neutral rounded-md hover:bg-background/80 text-sm text-gray-600 dark:text-gray-400 transition-colors">
class="flex items-center gap-2 px-3 py-2 bg-background border border-neutral rounded-md hover:bg-background/80 text-sm text-base/70 transition-colors">
<svg viewBox="0 -70 256 256" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid" fill="#000000" class="size-4">
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
@@ -747,7 +743,7 @@
<button type="button"
wire:click="quickAddDnsServers('google')"
@click="showAddDnsModal = false"
class="flex items-center gap-2 px-3 py-2 bg-background border border-neutral rounded-md hover:bg-background/80 text-sm text-gray-600 dark:text-gray-400 transition-colors">
class="flex items-center gap-2 px-3 py-2 bg-background border border-neutral rounded-md hover:bg-background/80 text-sm text-base/70 transition-colors">
<svg viewBox="-3 0 262 262" xmlns="http://www.w3.org/2000/svg" preserveAspectRatio="xMidYMid" fill="currentColor" class="size-4">
<g id="SVGRepo_bgCarrier" stroke-width="0"></g>
<g id="SVGRepo_tracerCarrier" stroke-linecap="round" stroke-linejoin="round"></g>
@@ -763,7 +759,7 @@
<button type="button"
wire:click="quickAddDnsServers('quad9')"
@click="showAddDnsModal = false"
class="flex items-center gap-2 px-3 py-2 bg-background border border-neutral rounded-md hover:bg-background/80 text-sm text-gray-600 dark:text-gray-400 transition-colors">
class="flex items-center gap-2 px-3 py-2 bg-background border border-neutral rounded-md hover:bg-background/80 text-sm text-base/70 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64" class="size-4">
<path d="M18.235 63.971h14.953l16.888-25.26c3.897-5.83 6.091-11.2 6.091-17.06C56.167 9.238 45.746 0 32.034 0S7.9 9.295 7.9 21.882c0 12.586 9.209 21.333 22.228 21.333.953 0 1.934-.058 2.887-.173L18.205 64Zm13.799-31.639c-6.467 0-11.23-4.36-11.23-10.537s4.763-10.537 11.23-10.537c6.466 0 11.23 4.302 11.23 10.537s-4.822 10.537-11.23 10.537" style="fill:#dc205e;stroke-width:2.88678"/>
<path d="M0 0h150v150H0Z" style="fill:none"/>
@@ -773,7 +769,7 @@
<button type="button"
wire:click="quickAddDnsServers('nat64')"
@click="showAddDnsModal = false"
class="flex items-center gap-2 px-3 py-2 bg-background border border-neutral rounded-md hover:bg-background/80 text-sm text-gray-600 dark:text-gray-400 transition-colors">
class="flex items-center gap-2 px-3 py-2 bg-background border border-neutral rounded-md hover:bg-background/80 text-sm text-base/70 transition-colors">
<svg xmlns="http://www.w3.org/2000/svg" height="512" width="512" viewBox="0 0 512 512" class="size-4">
<circle style="fill:#FFDA44;" cx="256" cy="256" r="256"></circle>
<path style="fill:#338AF3;" d="M0,256C0,114.616,114.616,0,256,0s256,114.616,256,256"></path>
@@ -783,7 +779,7 @@
</div>
</div>
<div class="space-y-2">
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">Nameserver</label>
<label class="text-xs font-semibold text-base/70">Nameserver</label>
<input type="text"
wire:model.defer="newDnsServer"
class="form-input w-full"
@@ -791,14 +787,14 @@
</div>
</div>
<div class="flex justify-end gap-2">
<x-proxmox::ui.button type="button" @click="showAddDnsModal = false" variant="secondary">
<x-button.secondary type="button" @click="showAddDnsModal = false">
Cancel
</x-proxmox::ui.button>
<x-proxmox::ui.button type="button"
</x-button.secondary>
<x-button.primary type="button"
wire:click="addDnsServer"
@click="showAddDnsModal = false" variant="primary">
@click="showAddDnsModal = false">
Add Nameserver
</x-proxmox::ui.button>
</x-button.primary>
</div>
</div>
</div>
@@ -806,18 +802,18 @@
</div>
</div>
<div class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-theme-sm p-4 space-y-4">
<div class="bg-background-secondary border border-neutral rounded-lg p-4 space-y-4">
<div class="flex items-center justify-between">
<div>
<div class="flex items-center gap-2 min-w-0">
<h3 class="text-lg font-semibold break-words">OS Type</h3>
<div class="relative" x-data="{ open: false }" x-cloak>
<button type="button" @click="open = true" class="text-base/50 hover:text-gray-600 dark:text-gray-400 transition-colors">
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors">
<x-ri-question-line class="size-4" />
</button>
<div x-show="open" x-transition.opacity class="fixed inset-0 z-[60] flex items-center justify-center p-4">
<div x-show="open" x-transition.opacity class="fixed inset-0 z-50 flex items-center justify-center p-4">
<div class="fixed inset-0 bg-black/40 backdrop-blur-sm" @click="open = false"></div>
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6"
<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"
x-on:keydown.escape.window="open = false">
<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" />
@@ -833,7 +829,7 @@
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
Specifies the operating system type for cloud-init metadata generation. This setting helps cloud-init apply the correct configuration format and optimizations for your guest OS.
</p>
<div class="pt-4 mt-4 border-t border-gray-200 dark:border-gray-800 space-y-2.5">
<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">Linux:</span> Standard Linux distributions (Ubuntu, Debian, CentOS, etc.). Uses standard cloud-init configuration format.
</p>
@@ -848,7 +844,7 @@
</div>
</div>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">Controls the <code>ostype</code> option used when generating cloud-init media.</p>
<p class="text-xs text-base/60 mt-1">Controls the <code>ostype</code> option used when generating cloud-init media.</p>
</div>
@if($this->osTypeSynced)
<span class="text-xs font-semibold text-success">Synced</span>
@@ -867,10 +863,10 @@
@endforeach
</select>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
<p class="text-xs text-base/60 mt-1">
Determines whether the guest receives nocloud or config-drive metadata. Choose the entry that matches your template.
</p>
<div class="bg-warning/10 border border-warning/30 rounded-lg p-3 text-xs text-gray-600 dark:text-gray-400 leading-relaxed mt-2">
<div class="bg-warning/10 border border-warning/30 rounded-lg p-3 text-xs text-base/70 leading-relaxed mt-2">
<strong>Tip:</strong> Pick a Windows option only for cloud-ready Windows images (cloudbase-init). Using a Windows entry on Linux templates prevents the nocloud ISO from mounting.
</div>
</div>
@@ -878,17 +874,17 @@
</div>
<div class="flex items-center gap-2 overflow-x-auto">
<x-proxmox::ui.button wire:click="refreshCloudInit" wire:loading.attr="disabled" variant="secondary">
<x-button.secondary wire:click="refreshCloudInit" wire:loading.attr="disabled">
<x-loading target="refreshCloudInit" />
Refresh
</x-proxmox::ui.button>
<x-proxmox::ui.button wire:click="resetCloudInit" :disabled="!$this->hasChanges" variant="secondary">
</x-button.secondary>
<x-button.secondary wire:click="resetCloudInit" :disabled="!$this->hasChanges">
Reset
</x-proxmox::ui.button>
<x-proxmox::ui.button wire:click="applyCloudInit" wire:loading.attr="disabled" :disabled="!$this->hasChanges" variant="primary">
</x-button.secondary>
<x-button.primary wire:click="applyCloudInit" wire:loading.attr="disabled" :disabled="!$this->hasChanges">
<x-loading target="applyCloudInit" />
Apply &amp; Regenerate
</x-proxmox::ui.button>
</x-button.primary>
</div>
</div>
</x-proxmox::layout>