Implement feature X to enhance user experience and optimize performance

This commit is contained in:
2025-12-17 18:36:48 -05:00
parent 205062dba8
commit a198421a70

View File

@@ -41,8 +41,8 @@
}
</script>
<div class="space-y-4 lg:space-y-6">
<div class="bg-info/10 border border-info/30 rounded-lg p-3 lg:p-4"
<div class="space-y-4 lg:space-y-6 px-0 md:px-0">
<div class="bg-gradient-to-r from-info/8 to-info/4 border border-info/25 rounded-xl p-4 lg:p-5 shadow-sm hover:shadow-md transition-shadow duration-200"
x-data="{
isCollapsed: localStorage.getItem('overview-notice-collapsed') === 'true',
isAdvancedView: localStorage.getItem('proxmox-view-mode') === 'advanced',
@@ -59,40 +59,40 @@
"
x-show="isAdvancedView"
x-transition>
<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-gray-500 dark:text-gray-400 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'" />
<div class="flex items-start gap-3.5">
<x-ri-information-line class="size-5 text-info flex-shrink-0 mt-0.5 opacity-80" />
<div class="flex-1 min-w-0">
<div class="flex items-center justify-between mb-2.5 gap-2">
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-100">Cloud-init Configuration Notice</h3>
<button @click="toggle()" type="button" class="text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors flex-shrink-0">
<x-ri-arrow-down-s-line x-bind:class="isCollapsed ? 'size-4 rotate-180 transition-transform duration-200' : 'size-4 transition-transform duration-200'" />
</button>
</div>
<div x-show="!isCollapsed" x-transition>
<p class="text-xs text-gray-600 dark:text-gray-400 leading-relaxed">
<strong>Hostname, password, and networking settings</strong> are configured automatically via <strong>cloud-init</strong>.
<div x-show="!isCollapsed" x-transition class="space-y-2">
<p class="text-xs text-gray-600 dark:text-gray-400 leading-relaxed font-medium">
<strong class="text-gray-700 dark:text-gray-200">Hostname, password, and networking settings</strong> are configured automatically via <strong class="text-gray-700 dark:text-gray-200">cloud-init</strong>.
This applies to standard operating system installations that support cloud-init. However, if you are:
</p>
<ul class="text-xs text-gray-600 dark:text-gray-400 leading-relaxed mt-2 ml-4 list-disc space-y-1">
<ul class="text-xs text-gray-600 dark:text-gray-400 leading-relaxed ml-5 list-disc space-y-1 opacity-90">
<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-gray-600 dark:text-gray-400 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 class="text-xs text-gray-600 dark:text-gray-400 leading-relaxed font-medium">
You will need to <strong class="text-gray-700 dark:text-gray-200">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" wire:poll.{{ $fastPolling ? '2s' : '30s' }}="checkStatus"
<div class="flex flex-col gap-1.5" wire:poll.{{ $fastPolling ? '2s' : '30s' }}="checkStatus"
wire:key="{{ $fastPolling ? 'fast' : 'slow' }}">
<div class="flex items-center gap-2 min-w-0">
<h2 class="text-lg sm:text-xl font-semibold break-words">Server Overview</h2>
<div class="flex items-center gap-2.5 min-w-0 group">
<h1 class="text-2xl sm:text-3xl font-bold text-gray-900 dark:text-white break-words tracking-tight">Server Overview</h1>
<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">
<x-ri-question-line class="size-4" />
<button type="button" @click="open = true" class="text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors opacity-70 hover:opacity-100">
<x-ri-question-line class="size-4.5" />
</button>
<!-- Modal Backdrop -->
<div x-show="open"
@@ -110,46 +110,71 @@
<!-- 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:enter-start="opacity-0 scale-95 translate-y-4"
x-transition:enter-end="opacity-100 scale-100 translate-y-0"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
x-transition:leave-start="opacity-100 scale-100 translate-y-0"
x-transition:leave-end="opacity-0 scale-95 translate-y-4"
@click.away="open = false"
@keydown.escape.window="open = false"
class="fixed inset-0 z-[60] 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-gray-200 dark:border-gray-800 shadow-2xl p-6">
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-700 shadow-2xl p-6 lg:p-8">
<button @click="open = false" class="absolute top-5 right-5 text-gray-400 dark:text-gray-500 hover:text-gray-600 dark:hover:text-gray-300 transition-colors opacity-75 hover:opacity-100">
<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">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10 opacity-80">
<x-ri-server-line class="size-5 text-primary" />
</div>
<h6 class="font-sans text-lg font-semibold leading-tight text-base">
Server Overview
</h6>
<div>
<h3 class="font-bold text-lg text-gray-900 dark:text-white">Server Overview</h3>
<p class="text-xs text-gray-500 dark:text-gray-400">Real-time monitoring</p>
</div>
</div>
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
<p class="text-sm text-gray-700 dark:text-gray-300 leading-relaxed mb-4 opacity-90">
Complete overview of your virtual machine including real-time resource usage, server status, network configuration, and DNS information.
</p>
<div class="pt-4 mt-4 border-t border-gray-200 dark:border-gray-800 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">Status:</span> Power state with real-time updates every 2-30 seconds.
</p>
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
<span class="font-semibold text-base/90">Resources:</span> CPU, memory usage, and uptime statistics from the hypervisor.
</p>
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
<span class="font-semibold text-base/90">Network:</span> IPv4/IPv6 addresses and DNS configuration for your VM.
</p>
<div class="pt-4 mt-4 border-t border-gray-200 dark:border-gray-700 space-y-3">
<div class="flex gap-3">
<div class="flex-shrink-0 mt-0.5">
<span class="flex items-center justify-center h-8 w-8 rounded-lg bg-primary/10 opacity-75">
<x-ri-dashboard-line class="h-4 w-4 text-primary" />
</span>
</div>
<div>
<p class="text-sm font-semibold text-gray-900 dark:text-white">Status</p>
<p class="text-xs text-gray-600 dark:text-gray-400 opacity-80">Power state with real-time updates every 2-30 seconds.</p>
</div>
</div>
<div class="flex gap-3">
<div class="flex-shrink-0 mt-0.5">
<span class="flex items-center justify-center h-8 w-8 rounded-lg bg-success/10 opacity-75">
<x-ri-cpu-line class="h-4 w-4 text-success" />
</span>
</div>
<div>
<p class="text-sm font-semibold text-gray-900 dark:text-white">Resources</p>
<p class="text-xs text-gray-600 dark:text-gray-400 opacity-80">CPU, memory usage, and uptime statistics from the hypervisor.</p>
</div>
</div>
<div class="flex gap-3">
<div class="flex-shrink-0 mt-0.5">
<span class="flex items-center justify-center h-8 w-8 rounded-lg bg-warning/10 opacity-75">
<x-ri-global-line class="h-4 w-4 text-warning" />
</span>
</div>
<div>
<p class="text-sm font-semibold text-gray-900 dark:text-white">Network</p>
<p class="text-xs text-gray-600 dark:text-gray-400 opacity-80">IPv4/IPv6 addresses and DNS configuration for your VM.</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<p class="text-sm text-gray-500 dark:text-gray-400">
<p class="text-sm text-gray-600 dark:text-gray-400 opacity-80 ml-0.5">
Real-time resource monitoring and server configuration details.
</p>
</div>
@@ -317,61 +342,65 @@
@endphp
<!-- Resource Stats Grid -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-3 lg:gap-4">
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 lg:gap-5">
@foreach($resourceAllocations as $resource)
<div class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-theme-sm p-3 lg:p-4">
<div class="flex items-center justify-between mb-3">
<h3 class="text-xs font-semibold text-gray-500 dark:text-gray-400 uppercase tracking-wider">{{ $resource['label'] }}</h3>
<div class="group bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-sm hover:shadow-md hover:border-gray-300 dark:hover:border-gray-700 transition-all duration-200 p-4 lg:p-5">
<div class="flex items-start justify-between mb-4">
<h3 class="text-xs font-bold text-gray-600 dark:text-gray-400 uppercase tracking-widest opacity-75">{{ $resource['label'] }}</h3>
@if(isset($resource['badge']))
<span class="text-xs font-semibold px-2 py-1 rounded {{ $resource['badge']['label'] === 'Healthy' ? 'bg-success/10 text-success' : ($resource['badge']['label'] === 'Watchlist' ? 'bg-warning/10 text-warning' : 'bg-error/10 text-error') }}">
<span class="text-xs font-bold px-2.5 py-1 rounded-full {{ $resource['badge']['label'] === 'Healthy' ? 'bg-success/15 text-success opacity-90' : ($resource['badge']['label'] === 'Watchlist' ? 'bg-warning/15 text-warning opacity-90' : 'bg-error/15 text-error opacity-90') }}">
{{ $resource['badge']['label'] }}
</span>
@endif
</div>
<div class="text-2xl font-bold mb-2">{{ $resource['usage_summary'] }}</div>
@if($resource['used_percent'] !== null)
<div class="relative w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden mb-2">
<div class="absolute inset-y-0 left-0 rounded-full"
style="width: {{ min(100, $resource['used_percent']) }}%; background: {{ $resource['gradient'] }};"></div>
<div class="mb-3">
<div class="text-3xl font-bold text-gray-900 dark:text-white tracking-tight mb-1">{{ $resource['usage_summary'] }}</div>
<p class="text-xs text-gray-600 dark:text-gray-400 opacity-75 font-medium">{{ $resource['entitlement_label'] }}</p>
</div>
@if($resource['used_percent'] !== null)
<div class="space-y-2">
<div class="relative w-full h-2.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden shadow-inner">
<div class="absolute inset-y-0 left-0 rounded-full transition-all duration-500 ease-out"
style="width: {{ min(100, $resource['used_percent']) }}%; background: {{ $resource['gradient'] }}; box-shadow: 0 0 20px {{ $resource['gradient'] }};"></div>
</div>
<div class="flex items-center justify-between">
<span class="text-xs font-semibold text-gray-700 dark:text-gray-300">{{ number_format($resource['used_percent'], 1) }}%</span>
@if($resource['used_percent'] > 100)
<span class="text-xs font-bold text-error opacity-85">Over entitlement</span>
@endif
</div>
</div>
<p class="text-xs text-gray-500 dark:text-gray-400">
{{ number_format($resource['used_percent'], 1) }}% of {{ $resource['entitlement_label'] }}
</p>
@else
<p class="text-xs text-gray-500 dark:text-gray-400 mt-2">
{{ $resource['entitlement_label'] }}
</p>
@endif
</div>
@endforeach
</div>
<!-- Main Grid Layout -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-3 lg:gap-4">
<div class="grid grid-cols-1 lg:grid-cols-2 gap-4 lg:gap-5">
<!-- Server Information Card -->
<div class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-theme-sm overflow-hidden">
<div class="flex items-center justify-between p-3 lg:p-4 bg-primary/5 border-b border-gray-200 dark:border-gray-800">
<div class="flex items-center gap-2">
<x-ri-server-fill class="size-5 text-primary" />
<h3 class="text-base sm:text-lg font-semibold">Server Information</h3>
<div class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-sm hover:shadow-md hover:border-gray-300 dark:hover:border-gray-700 transition-all duration-200 overflow-hidden">
<div class="flex items-center justify-between p-4 lg:p-5 bg-gradient-to-r from-primary/8 to-primary/4 border-b border-primary/20">
<div class="flex items-center gap-2.5">
<x-ri-server-fill class="size-5 text-primary opacity-80" />
<h3 class="text-base sm:text-lg font-bold text-gray-900 dark:text-white">Server Information</h3>
</div>
</div>
<div class="p-3 lg:p-4">
<div class="space-y-3">
<div class="p-4 lg:p-5">
<div class="space-y-3.5">
<!-- Status -->
<div class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800">
<div class="flex items-center gap-2 text-sm font-semibold text-gray-600 dark:text-gray-400">
<x-ri-pulse-line class="size-4" />
<div class="flex items-center justify-between py-2.5 px-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
<div class="flex items-center gap-2 text-sm font-semibold text-gray-600 dark:text-gray-400 opacity-80">
<x-ri-pulse-line class="size-4 opacity-75" />
<span>Status</span>
</div>
<div>
@if ($status && $status->status === 'running')
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-semibold bg-success/10 text-success rounded-full">
<span class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold bg-success/15 text-success rounded-full opacity-90">
<span class="w-1.5 h-1.5 bg-success rounded-full animate-pulse"></span>
Running
</span>
@else
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-semibold bg-warning/10 text-warning rounded-full">
<span class="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold bg-warning/15 text-warning rounded-full opacity-90">
<x-ri-stop-circle-line class="size-3" />
Stopped
</span>
@@ -379,22 +408,22 @@
</div>
</div>
<!-- Operating System -->
<div class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800">
<div class="flex items-center gap-2 text-sm font-semibold text-gray-600 dark:text-gray-400">
<x-ri-ubuntu-line class="size-4" />
<div class="flex items-center justify-between py-2.5 px-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
<div class="flex items-center gap-2 text-sm font-semibold text-gray-600 dark:text-gray-400 opacity-80">
<x-ri-ubuntu-line class="size-4 opacity-75" />
<span>Operating System</span>
</div>
<span class="text-sm">{{ $server->os->name }}</span>
<span class="text-sm text-gray-900 dark:text-white font-medium opacity-90">{{ $server->os->name }}</span>
</div>
<!-- Hostname -->
<div class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800" x-data="{ showHostnameEditModal: @entangle('isEditingHostname') }">
<div class="flex items-center gap-2 text-sm font-semibold text-gray-600 dark:text-gray-400">
<x-ri-computer-line class="size-4" />
<div class="flex items-center justify-between py-2.5 px-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors" x-data="{ showHostnameEditModal: @entangle('isEditingHostname') }">
<div class="flex items-center gap-2 text-sm font-semibold text-gray-600 dark:text-gray-400 opacity-80">
<x-ri-computer-line class="size-4 opacity-75" />
<span>Hostname</span>
</div>
<div class="flex items-center gap-2">
<code class="text-sm">{{ $server->hostname ?? $status->name ?? 'N/A' }}</code>
<button wire:click="startEditingHostname" type="button" class="text-primary hover:text-primary/80 transition-colors">
<code class="text-sm text-gray-900 dark:text-white font-mono opacity-90">{{ $server->hostname ?? $status->name ?? 'N/A' }}</code>
<button wire:click="startEditingHostname" type="button" class="text-primary opacity-70 hover:opacity-100 transition-opacity">
<x-ri-edit-line class="size-4" />
</button>
</div>
@@ -462,14 +491,14 @@
<!-- Primary IPv4 Address -->
@if($server->primaryIpv4)
<div class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800" x-data="{ copied: false, copyText: '{{ $server->primaryIpv4->ip }}', copy() { navigator.clipboard.writeText(this.copyText); this.copied = true; setTimeout(() => this.copied = false, 2000); } }">
<div class="flex items-center gap-2 text-sm font-semibold text-gray-600 dark:text-gray-400">
<x-ri-global-line class="size-4" />
<div class="flex items-center justify-between py-2.5 px-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors" x-data="{ copied: false, copyText: '{{ $server->primaryIpv4->ip }}', copy() { navigator.clipboard.writeText(this.copyText); this.copied = true; setTimeout(() => this.copied = false, 2000); } }">
<div class="flex items-center gap-2 text-sm font-semibold text-gray-600 dark:text-gray-400 opacity-80">
<x-ri-global-line class="size-4 opacity-75" />
<span>IPv4 Address</span>
</div>
<div class="flex items-center gap-2">
<code class="text-sm">{{ $server->primaryIpv4->ip }}</code>
<button @click="copy()" type="button" class="text-success hover:text-success/80 transition-colors">
<code class="text-sm text-gray-900 dark:text-white font-mono opacity-90">{{ $server->primaryIpv4->ip }}</code>
<button @click="copy()" type="button" class="text-success opacity-70 hover:opacity-100 transition-opacity">
<x-ri-check-fill x-show="copied" class="size-4" />
<x-ri-file-copy-line x-show="!copied" class="size-4" />
</button>
@@ -479,14 +508,14 @@
<!-- Primary IPv6 Address -->
@if($server->primaryIpv6)
<div class="flex items-center justify-between py-2 border-b border-gray-200 dark:border-gray-800" x-data="{ copied: false, copyText: '{{ $server->primaryIpv6->ip }}', copy() { navigator.clipboard.writeText(this.copyText); this.copied = true; setTimeout(() => this.copied = false, 2000); } }">
<div class="flex items-center gap-2 text-sm font-semibold text-gray-600 dark:text-gray-400">
<x-ri-global-line class="size-4" />
<div class="flex items-center justify-between py-2.5 px-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors" x-data="{ copied: false, copyText: '{{ $server->primaryIpv6->ip }}', copy() { navigator.clipboard.writeText(this.copyText); this.copied = true; setTimeout(() => this.copied = false, 2000); } }">
<div class="flex items-center gap-2 text-sm font-semibold text-gray-600 dark:text-gray-400 opacity-80">
<x-ri-global-line class="size-4 opacity-75" />
<span>IPv6 Address</span>
</div>
<div class="flex items-center gap-2">
<code class="text-xs truncate max-w-[200px]">{{ $server->primaryIpv6->ip }}</code>
<button @click="copy()" type="button" class="text-success hover:text-success/80 transition-colors flex-shrink-0">
<code class="text-xs text-gray-900 dark:text-white font-mono truncate max-w-[200px] opacity-90">{{ $server->primaryIpv6->ip }}</code>
<button @click="copy()" type="button" class="text-success opacity-70 hover:opacity-100 transition-opacity flex-shrink-0">
<x-ri-check-fill x-show="copied" class="size-4" />
<x-ri-file-copy-line x-show="!copied" class="size-4" />
</button>
@@ -495,9 +524,9 @@
@endif
<!-- Root Password -->
<div class="flex items-center justify-between py-2" x-data="{ showPasswordViewModal: false, showPasswordEditModal: @entangle('isEditingPassword') }">
<div class="flex items-center gap-2 text-sm font-semibold text-gray-600 dark:text-gray-400">
<x-ri-lock-password-line class="size-4" />
<div class="flex items-center justify-between py-2.5 px-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors" x-data="{ showPasswordViewModal: false, showPasswordEditModal: @entangle('isEditingPassword') }">
<div class="flex items-center gap-2 text-sm font-semibold text-gray-600 dark:text-gray-400 opacity-80">
<x-ri-lock-password-line class="size-4 opacity-75" />
<span>Root Password</span>
</div>
<div class="flex items-center gap-2">
@@ -505,15 +534,15 @@
@if(isset($server->password) && $server->password)
••••••••
@else
<span class="inline-flex items-center px-2.5 py-1 text-xs font-semibold bg-info/10 text-info rounded-full">Emailed</span>
<span class="inline-flex items-center px-2.5 py-1 text-xs font-semibold bg-info/15 text-info rounded-full opacity-90">Emailed</span>
@endif
</span>
@if(isset($server->password) && $server->password)
<button type="button" class="text-gray-500 hover:text-base transition-colors" @click="showPasswordViewModal = true">
<button type="button" class="text-gray-500 opacity-70 hover:opacity-100 transition-opacity" @click="showPasswordViewModal = true">
<x-ri-eye-line class="size-4" />
</button>
@endif
<button type="button" class="text-primary hover:text-primary/80 transition-colors" wire:click="startEditingPassword">
<button type="button" class="text-primary opacity-70 hover:opacity-100 transition-opacity" wire:click="startEditingPassword">
<x-ri-edit-line class="size-4" />
</button>
</div>
@@ -663,35 +692,46 @@
</div>
</div>
<div>
<div style="background-image: url('https://billing.virtnet.bond/assets/images/dns.jpeg'); background-size: cover; background-position: center; height: 3rem;" class="rounded-t-lg border-t border-x border-gray-200 dark:border-gray-700"></div>
<div class="bg-white dark:bg-gray-800 border-x border-gray-200 dark:border-gray-700 p-3 space-y-3">
<div class="flex items-center gap-2 min-w-0">
<h2 class="text-base font-semibold">DNS</h2>
<div class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-sm hover:shadow-md hover:border-gray-300 dark:hover:border-gray-700 transition-all duration-200 overflow-hidden">
<div style="background-image: url('https://billing.virtnet.bond/assets/images/dns.jpeg'); background-size: cover; background-position: center; height: 3rem;" class="border-b border-gray-200 dark:border-gray-800"></div>
<div class="p-4 lg:p-5 space-y-4">
<div class="flex items-center gap-2.5">
<x-ri-global-line class="size-5 text-primary opacity-80" />
<h2 class="text-base sm:text-lg font-bold text-gray-900 dark:text-white">DNS Configuration</h2>
<div class="relative" x-data="{ open: false }" x-cloak>
<button type="button" @click="open = true" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<button type="button" @click="open = true" class="text-gray-400 opacity-70 hover:opacity-100 transition-opacity">
<x-ri-question-line class="size-4" />
</button>
<div x-show="open" @click="open = false" @keydown.escape.window="open = false"
class="fixed inset-0 z-[60] bg-gray-900 bg-opacity-50" style="display: none;"></div>
class="fixed inset-0 z-[60] bg-gray-900 bg-opacity-50 backdrop-blur-sm" style="display: none;"></div>
<div x-show="open" @click.away="open = false"
class="fixed inset-0 z-[60] flex items-center justify-center p-4" style="display: none;">
<div class="relative w-full max-w-xl max-h-[90vh] overflow-y-auto bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-xl p-4">
<button @click="open = false" class="absolute top-3 right-3 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<x-ri-close-line class="size-4" />
<div x-transition:enter="transition ease-out duration-200 translate-y-4"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
class="relative w-full max-w-xl max-h-[90vh] overflow-y-auto bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6 lg:p-8">
<button @click="open = false" class="absolute top-4 right-4 text-gray-400 opacity-75 hover:opacity-100 transition-opacity">
<x-ri-close-line class="size-5" />
</button>
<div class="flex items-center gap-2 mb-3 pr-6">
<div class="flex items-center justify-center w-8 h-8 rounded bg-blue-50 dark:bg-blue-900">
<x-ri-global-line class="size-4 text-primary" />
<div class="flex items-start gap-3 mb-4 pr-8">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/15 flex-shrink-0">
<x-ri-global-line class="size-5 text-primary opacity-80" />
</div>
<div>
<h6 class="text-lg font-bold text-gray-900 dark:text-white">Domain Name System (DNS)</h6>
<p class="text-xs text-gray-600 dark:text-gray-400 opacity-80 mt-1">DNS configuration for your virtual server</p>
</div>
<h6 class="text-base font-semibold">Domain Name System (DNS)</h6>
</div>
<p class="text-sm text-gray-700 dark:text-gray-300 mb-3">
DNS info for your VM. Displays hostname and DNS configuration for the guest OS.
<p class="text-sm text-gray-700 dark:text-gray-300 opacity-90 mb-4">
DNS info for your VM. Displays hostname and DNS configuration for the guest OS. Multiple DNS providers are available for flexible hostname resolution.
</p>
<div class="pt-3 border-t border-gray-200 dark:border-gray-700">
<p class="text-xs text-gray-600 dark:text-gray-400">
<strong>Note:</strong> DNS configured via cloud-init. For Windows/manual, configure within guest OS.
<div class="bg-info/10 border border-info/25 rounded-lg p-4 mb-4">
<p class="text-xs text-info font-semibold mb-2 opacity-90">
<x-ri-info-line class="inline size-3.5 mr-1" />
Important Note
</p>
<p class="text-xs text-gray-600 dark:text-gray-300 opacity-80">
DNS is configured via cloud-init. For Windows or manual configuration, you'll need to configure DNS directly within the guest OS.
</p>
</div>
</div>
@@ -699,123 +739,180 @@
</div>
</div>
<div class="text-sm text-gray-600 dark:text-gray-400">
<p class="font-medium">IPv6 DNS: {{ $server->primaryIpv6->ip }}</p>
<div class="space-y-3">
<div class="text-sm font-semibold text-gray-700 dark:text-gray-300 opacity-80">Primary IPv6: <code class="text-xs font-mono text-gray-900 dark:text-white opacity-90">{{ $server->primaryIpv6->ip }}</code></div>
<!-- Virtnet -->
<div class="flex items-center gap-1 ml-4 text-xs">
<span>{{ str_replace(':', '-', $server->primaryIpv6->ip) . '.virtnet.host' }} <small class="text-gray-500">(Internal)</small></span>
<div class="flex items-center justify-between p-3 rounded-lg bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
<div class="min-w-0 flex-1">
<span class="text-xs font-semibold text-gray-600 dark:text-gray-400 opacity-80 block mb-1">Virtnet Internal</span>
<code class="text-sm text-gray-900 dark:text-white font-mono opacity-90">{{ str_replace(':', '-', $server->primaryIpv6->ip) . '.virtnet.host' }}</code>
</div>
<div x-data="{
copied: false,
copyText: '{{ str_replace(':', '-', $server->primaryIpv6->ip) . '.virtnet.host' }}',
copyToClipboard() { navigator.clipboard.writeText(this.copyText); this.copied = true; setTimeout(() => this.copied = false, 2000); }
}">
<button @click="copyToClipboard()" type="button" class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 relative inline-flex items-center justify-center">
<span x-show="copied" x-transition class="absolute"><x-ri-check-fill class="size-3 text-success" /></span>
<span x-show="!copied" x-transition class="absolute"><x-ri-file-copy-fill class="size-3" /></span>
<button @click="copyToClipboard()" type="button" class="text-gray-500 opacity-70 hover:opacity-100 transition-opacity ml-3 flex-shrink-0">
<span x-show="copied" x-transition class="inline"><x-ri-check-fill class="size-4 text-success" /></span>
<span x-show="!copied" x-transition class="inline"><x-ri-file-copy-line class="size-4" /></span>
</button>
</div>
</div>
<!-- nip.io -->
<div class="flex items-center gap-1 ml-4 text-xs">
<span>{{ str_replace(':', '-', $server->primaryIpv6->ip) . '.nip.io' }} <small class="text-gray-500">(<a href="https://nip.io" target="_blank" class="underline">nip.io</a>)</small></span>
<div class="flex items-center justify-between p-3 rounded-lg bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
<div class="min-w-0 flex-1">
<span class="text-xs font-semibold text-gray-600 dark:text-gray-400 opacity-80 block mb-1">nip.io (Public)</span>
<code class="text-sm text-gray-900 dark:text-white font-mono opacity-90">{{ str_replace(':', '-', $server->primaryIpv6->ip) . '.nip.io' }}</code>
</div>
<div x-data="{
copied: false,
copyText: '{{ str_replace(':', '-', $server->primaryIpv6->ip) . '.nip.io' }}',
copyToClipboard() { navigator.clipboard.writeText(this.copyText); this.copied = true; setTimeout(() => this.copied = false, 2000); }
}">
<button @click="copyToClipboard()" type="button" class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 relative inline-flex items-center justify-center">
<span x-show="copied" x-transition class="absolute"><x-ri-check-fill class="size-3 text-success" /></span>
<span x-show="!copied" x-transition class="absolute"><x-ri-file-copy-fill class="size-3" /></span>
<button @click="copyToClipboard()" type="button" class="text-gray-500 opacity-70 hover:opacity-100 transition-opacity ml-3 flex-shrink-0">
<span x-show="copied" x-transition class="inline"><x-ri-check-fill class="size-4 text-success" /></span>
<span x-show="!copied" x-transition class="inline"><x-ri-file-copy-line class="size-4" /></span>
</button>
</div>
</div>
<!-- sslip.io -->
<div class="flex items-center gap-1 ml-4 text-xs">
<span>{{ str_replace(':', '-', $server->primaryIpv6->ip) . '.sslip.io' }} <small class="text-gray-500">(<a href="https://sslip.io" target="_blank" class="underline">sslip.io</a>)</small></span>
<div class="flex items-center justify-between p-3 rounded-lg bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
<div class="min-w-0 flex-1">
<span class="text-xs font-semibold text-gray-600 dark:text-gray-400 opacity-80 block mb-1">sslip.io (Public)</span>
<code class="text-sm text-gray-900 dark:text-white font-mono opacity-90">{{ str_replace(':', '-', $server->primaryIpv6->ip) . '.sslip.io' }}</code>
</div>
<div x-data="{
copied: false,
copyText: '{{ str_replace(':', '-', $server->primaryIpv6->ip) . '.sslip.io' }}',
copyToClipboard() { navigator.clipboard.writeText(this.copyText); this.copied = true; setTimeout(() => this.copied = false, 2000); }
}">
<button @click="copyToClipboard()" type="button" class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 relative inline-flex items-center justify-center">
<span x-show="copied" x-transition class="absolute"><x-ri-check-fill class="size-3 text-success" /></span>
<span x-show="!copied" x-transition class="absolute"><x-ri-file-copy-fill class="size-3" /></span>
<button @click="copyToClipboard()" type="button" class="text-gray-500 opacity-70 hover:opacity-100 transition-opacity ml-3 flex-shrink-0">
<span x-show="copied" x-transition class="inline"><x-ri-check-fill class="size-4 text-success" /></span>
<span x-show="!copied" x-transition class="inline"><x-ri-file-copy-line class="size-4" /></span>
</button>
</div>
</div>
<!-- backname.io -->
<div class="flex items-center gap-1 ml-4 text-xs">
<span>{{ str_replace(':', '-', $server->primaryIpv6->ip) . '.backname.io' }} <small class="text-gray-500">(<a href="https://backname.io" target="_blank" class="underline">backname.io</a>)</small></span>
<div class="flex items-center justify-between p-3 rounded-lg bg-gray-50 dark:bg-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700 transition-colors">
<div class="min-w-0 flex-1">
<span class="text-xs font-semibold text-gray-600 dark:text-gray-400 opacity-80 block mb-1">backname.io (Public)</span>
<code class="text-sm text-gray-900 dark:text-white font-mono opacity-90">{{ str_replace(':', '-', $server->primaryIpv6->ip) . '.backname.io' }}</code>
</div>
<div x-data="{
copied: false,
copyText: '{{ str_replace(':', '-', $server->primaryIpv6->ip) . '.backname.io' }}',
copyToClipboard() { navigator.clipboard.writeText(this.copyText); this.copied = true; setTimeout(() => this.copied = false, 2000); }
}">
<button @click="copyToClipboard()" type="button" class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300 relative inline-flex items-center justify-center">
<span x-show="copied" x-transition class="absolute"><x-ri-check-fill class="size-3 text-success" /></span>
<span x-show="!copied" x-transition class="absolute"><x-ri-file-copy-fill class="size-3" /></span>
<button @click="copyToClipboard()" type="button" class="text-gray-500 opacity-70 hover:opacity-100 transition-opacity ml-3 flex-shrink-0">
<span x-show="copied" x-transition class="inline"><x-ri-check-fill class="size-4 text-success" /></span>
<span x-show="!copied" x-transition class="inline"><x-ri-file-copy-line class="size-4" /></span>
</button>
</div>
</div>
</div>
</div>
<div class="bg-blue-50 dark:bg-blue-950 border border-blue-200 dark:border-blue-800 p-3 rounded-b-lg text-xs text-gray-600 dark:text-gray-400">
<p class="font-semibold"><code>@</code> = root domain → {{ $server->primaryIpv6->ip }} | <code>*.@</code> = wildcard subdomains → same IP</p>
<p class="mt-1 text-gray-500 dark:text-gray-500">rDNS PTR: No PTR records via RIR systems except direct contract customers.</p>
<div class="bg-gradient-to-r from-info/8 to-info/4 border-t border-info/20 p-4 rounded-b-2xl">
<p class="text-xs font-semibold text-gray-700 dark:text-gray-300 opacity-90 mb-2">
<x-ri-lightbulb-line class="inline size-3.5 mr-1 opacity-75" />
DNS Resolution
</p>
<p class="text-xs text-gray-600 dark:text-gray-400 opacity-80">
<code class="font-mono">@</code> = root domain → {{ $server->primaryIpv6->ip }} | <code class="font-mono">*.@</code> = wildcard subdomains → same IP
</p>
<p class="text-xs text-gray-600 dark:text-gray-400 opacity-75 mt-2">
rDNS PTR: No PTR records via RIR systems except direct contract customers.
</p>
</div>
</div>
@if(stripos($server->os->name, 'Windows') !== false)
<div>
<div style="background-image: url('https://billing.virtnet.bond/assets/images/windows.jpg'); background-size: cover; background-position: center; height: 3rem;" class="rounded-t-lg border-t border-x border-gray-200 dark:border-gray-700"></div>
<div class="bg-blue-50 dark:bg-blue-950 border border-blue-200 dark:border-blue-800 p-3 rounded-b-lg">
<div class="flex items-center gap-2">
<h2 class="text-base font-semibold">Windows Server</h2>
<div class="relative" x-data="{ open: false }" x-cloak>
<button type="button" @click="open = true" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<x-ri-question-line class="size-4" />
</button>
<div x-show="open" @click="open = false" @keydown.escape.window="open = false"
class="fixed inset-0 z-[60] bg-gray-900 bg-opacity-50 backdrop-blur-sm" style="display: none;"></div>
<div x-show="open" @click.away="open = false"
class="fixed inset-0 z-[60] flex items-center justify-center p-4" style="display: none;">
<div class="relative w-full max-w-xl max-h-[90vh] overflow-y-auto bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-xl p-4">
<button @click="open = false" class="absolute top-3 right-3 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<x-ri-close-line class="size-4" />
</button>
<div class="flex items-center gap-2 mb-3 pr-6">
<div class="flex items-center justify-center w-8 h-8 rounded bg-blue-50 dark:bg-blue-900">
<x-ri-windows-line class="size-4 text-primary" />
<div class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-sm hover:shadow-md hover:border-gray-300 dark:hover:border-gray-700 transition-all duration-200 overflow-hidden">
<div style="background-image: url('https://billing.virtnet.bond/assets/images/windows.jpg'); background-size: cover; background-position: center; height: 3rem;" class="border-b border-gray-200 dark:border-gray-800"></div>
<div class="p-4 lg:p-5 space-y-4">
<div class="flex items-center gap-2.5">
<x-ri-windows-line class="size-5 text-primary opacity-80" />
<h2 class="text-base sm:text-lg font-bold text-gray-900 dark:text-white">Windows Server</h2>
<div class="relative" x-data="{ open: false }" x-cloak>
<button type="button" @click="open = true" class="text-gray-400 opacity-70 hover:opacity-100 transition-opacity">
<x-ri-question-line class="size-4" />
</button>
<div x-show="open" @click="open = false" @keydown.escape.window="open = false"
class="fixed inset-0 z-[60] bg-gray-900 bg-opacity-50 backdrop-blur-sm" style="display: none;"></div>
<div x-show="open" @click.away="open = false"
class="fixed inset-0 z-[60] flex items-center justify-center p-4" style="display: none;">
<div x-transition:enter="transition ease-out duration-200 translate-y-4"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
class="relative w-full max-w-xl max-h-[90vh] overflow-y-auto bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6 lg:p-8">
<button @click="open = false" class="absolute top-4 right-4 text-gray-400 opacity-75 hover:opacity-100 transition-opacity">
<x-ri-close-line class="size-5" />
</button>
<div class="flex items-start gap-3 mb-4 pr-8">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/15 flex-shrink-0">
<x-ri-windows-line class="size-5 text-primary opacity-80" />
</div>
<div>
<h6 class="text-lg font-bold text-gray-900 dark:text-white">Windows Server Configuration</h6>
<p class="text-xs text-gray-600 dark:text-gray-400 opacity-80 mt-1">Manual configuration required</p>
</div>
</div>
<p class="text-sm text-gray-700 dark:text-gray-300 opacity-90 mb-4">
Windows doesn't support cloud-init for automated configuration. You'll need to manually configure networking, hostname, and system settings during or after installation.
</p>
<div class="space-y-3">
<div class="bg-info/10 border border-info/25 rounded-lg p-3.5">
<p class="text-xs font-semibold text-info opacity-90 mb-2">
<x-ri-network-line class="inline size-3.5 mr-1 opacity-75" />
Network Configuration
</p>
<p class="text-xs text-gray-600 dark:text-gray-300 opacity-80">Use static IPs or DHCP via Windows Network Configuration. Cloud-init doesn't configure networking on Windows.</p>
</div>
<div class="bg-warning/10 border border-warning/25 rounded-lg p-3.5">
<p class="text-xs font-semibold text-warning opacity-90 mb-2">
<x-ri-shield-line class="inline size-3.5 mr-1 opacity-75" />
Licensing
</p>
<p class="text-xs text-gray-600 dark:text-gray-300 opacity-80">No license key provided. You must maintain compliant licensing for your Windows installation.</p>
</div>
<div class="bg-error/10 border border-error/25 rounded-lg p-3.5">
<p class="text-xs font-semibold text-error opacity-90 mb-2">
<x-ri-alert-line class="inline size-3.5 mr-1 opacity-75" />
Compliance Audits
</p>
<p class="text-xs text-gray-600 dark:text-gray-300 opacity-80">Periodic audits may occur. Non-compliant installations are subject to suspension.</p>
</div>
</div>
<h6 class="text-base font-semibold">Windows Server</h6>
</div>
<p class="text-sm text-gray-700 dark:text-gray-300 mb-3">Windows doesn't support cloud-init. Manual config required.</p>
<div class="pt-3 border-t border-gray-200 dark:border-gray-700 space-y-2 text-xs text-gray-600 dark:text-gray-400">
<p><strong>Manual Config:</strong> Configure networking, hostname, and users during/after install.</p>
<p><strong>Network:</strong> Use static IPs or DHCP via Windows Network Configuration.</p>
<p><strong>Remote Access:</strong> Enable RDP within Windows for remote server access.</p>
</div>
</div>
</div>
</div>
</div>
<div class="mt-2 space-y-2 text-xs">
<div class="border-l-4 border-blue-400 bg-blue-100 dark:bg-blue-900 p-2 rounded-r">
<p class="font-semibold text-gray-700 dark:text-gray-200">IPv4/IPv6 Setup:</p>
<p class="text-gray-600 dark:text-gray-400">Cloud-init doesn't configure networking on Windows. Configure addresses manually.</p>
</div>
<div class="border-l-4 border-blue-400 bg-blue-100 dark:bg-blue-900 p-2 rounded-r">
<p class="font-semibold text-gray-700 dark:text-gray-200">Licensing:</p>
<p class="text-gray-600 dark:text-gray-400">No license key provided. User must maintain compliant licensing.</p>
</div>
<div class="border-l-4 border-blue-400 bg-blue-100 dark:bg-blue-900 p-2 rounded-r">
<p class="font-semibold text-gray-700 dark:text-gray-200">Audits:</p>
<p class="text-gray-600 dark:text-gray-400">Periodic audits may occur. Non-compliant installations subject to suspension.</p>
<div class="space-y-3">
<div class="flex items-start gap-3 p-3.5 rounded-lg border border-info/20 bg-info/5 hover:bg-info/10 transition-colors">
<x-ri-check-line class="size-4 text-info opacity-80 flex-shrink-0 mt-0.5" />
<div class="min-w-0">
<p class="text-xs font-semibold text-gray-900 dark:text-white opacity-90">IPv4/IPv6 Setup</p>
<p class="text-xs text-gray-600 dark:text-gray-400 opacity-80 mt-1">Configure addresses manually within Windows Network Configuration.</p>
</div>
</div>
<div class="flex items-start gap-3 p-3.5 rounded-lg border border-warning/20 bg-warning/5 hover:bg-warning/10 transition-colors">
<x-ri-shield-check-line class="size-4 text-warning opacity-80 flex-shrink-0 mt-0.5" />
<div class="min-w-0">
<p class="text-xs font-semibold text-gray-900 dark:text-white opacity-90">Licensing Compliance</p>
<p class="text-xs text-gray-600 dark:text-gray-400 opacity-80 mt-1">You're responsible for maintaining valid Windows licensing for your installation.</p>
</div>
</div>
<div class="flex items-start gap-3 p-3.5 rounded-lg border border-error/20 bg-error/5 hover:bg-error/10 transition-colors">
<x-ri-alert-line class="size-4 text-error opacity-80 flex-shrink-0 mt-0.5" />
<div class="min-w-0">
<p class="text-xs font-semibold text-gray-900 dark:text-white opacity-90">Audit Risks</p>
<p class="text-xs text-gray-600 dark:text-gray-400 opacity-80 mt-1">Non-compliant installations may be suspended after verification audits.</p>
</div>
</div>
</div>
</div>
</div>
@@ -824,55 +921,73 @@
<!-- Notes Section -->
<div class="mt-4">
<div id="notes-section" class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg p-4 flex flex-col" x-data="{ showExportErrorModal: false }" x-cloak>
<div class="flex items-center justify-between mb-3">
<div class="flex items-center gap-2">
<h2 class="text-base font-semibold">Notes</h2>
<div id="notes-section" class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-sm hover:shadow-md hover:border-gray-300 dark:hover:border-gray-700 transition-all duration-200 p-4 lg:p-5 flex flex-col" x-data="{ showExportErrorModal: false }" x-cloak>
<div class="flex items-center justify-between mb-4">
<div class="flex items-center gap-2.5">
<x-ri-file-text-line class="size-5 text-primary opacity-80" />
<h2 class="text-base sm:text-lg font-bold text-gray-900 dark:text-white">Notes</h2>
<div class="relative" x-data="{ open: false }" x-cloak>
<button type="button" @click="open = true" class="text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<button type="button" @click="open = true" class="text-gray-400 opacity-70 hover:opacity-100 transition-opacity">
<x-ri-question-line class="size-4" />
</button>
<div x-show="open" @click="open = false" @keydown.escape.window="open = false"
class="fixed inset-0 z-[60] bg-gray-900 bg-opacity-50 backdrop-blur-sm" style="display: none;"></div>
<div x-show="open" @click.away="open = false" @keydown.escape.window="open = false"
class="fixed inset-0 z-[60] flex items-center justify-center p-4" style="display: none;">
<div class="relative w-full max-w-xl max-h-[90vh] overflow-y-auto bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-xl p-4">
<button @click="open = false" class="absolute top-3 right-3 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<x-ri-close-line class="size-4" />
<div x-transition:enter="transition ease-out duration-200 translate-y-4"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
class="relative w-full max-w-xl max-h-[90vh] overflow-y-auto bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6 lg:p-8">
<button @click="open = false" class="absolute top-4 right-4 text-gray-400 opacity-75 hover:opacity-100 transition-opacity">
<x-ri-close-line class="size-5" />
</button>
<div class="flex items-center gap-2 mb-3 pr-6">
<div class="flex items-center justify-center w-8 h-8 rounded bg-blue-50 dark:bg-blue-900">
<x-ri-file-text-line class="size-4 text-primary" />
<div class="flex items-start gap-3 mb-4 pr-8">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/15 flex-shrink-0">
<x-ri-file-text-line class="size-5 text-primary opacity-80" />
</div>
<div>
<h6 class="text-lg font-bold text-gray-900 dark:text-white">Server Notes</h6>
<p class="text-xs text-gray-600 dark:text-gray-400 opacity-80 mt-1">Store and reference information about your server</p>
</div>
<h6 class="text-base font-semibold">Notes</h6>
</div>
<p class="text-sm text-gray-700 dark:text-gray-300 mb-3">Store notes about this VM with Markdown support.</p>
<div class="pt-3 border-t border-gray-200 dark:border-gray-700 space-y-2 text-xs text-gray-600 dark:text-gray-400">
<p><strong>Markdown:</strong> Headers, lists, links, code blocks supported.</p>
<p><strong>Persistence:</strong> Notes saved to DB but deleted on reinstall.</p>
<p><strong>Uses:</strong> Config docs, IPs, credentials, install steps.</p>
<p class="text-sm text-gray-700 dark:text-gray-300 opacity-90 mb-4">
Store notes about this VM with full Markdown support. Notes are persisted to the database but will be cleared when you reinstall the server.
</p>
<div class="space-y-3">
<div class="bg-info/10 border border-info/25 rounded-lg p-3.5">
<p class="text-xs font-semibold text-info opacity-90 mb-2">
<x-ri-markdown-line class="inline size-3.5 mr-1 opacity-75" />
Markdown Support
</p>
<p class="text-xs text-gray-600 dark:text-gray-300 opacity-80">Headers, lists, links, code blocks, and formatting are all supported.</p>
</div>
<div class="bg-warning/10 border border-warning/25 rounded-lg p-3.5">
<p class="text-xs font-semibold text-warning opacity-90 mb-2">
<x-ri-database-line class="inline size-3.5 mr-1 opacity-75" />
Persistence
</p>
<p class="text-xs text-gray-600 dark:text-gray-300 opacity-80">Notes are saved to the database. They will be deleted when you reinstall the server.</p>
</div>
</div>
</div>
</div>
</div>
<span class="text-xs text-gray-500 dark:text-gray-400">(Markdown • Cleared on reinstall)</span>
</div>
<div class="flex items-center gap-2">
<button type="button" @click="exportNotesToCSV()"
class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 px-2 py-1 rounded text-xs font-semibold hover:bg-gray-100 dark:hover:bg-gray-800 flex items-center gap-1"
title="Export notes">
<x-ri-download-line class="size-3" />
Export
</button>
<span class="text-xs text-gray-600 dark:text-gray-400 opacity-75">(Markdown • Cleared on reinstall)</span>
</div>
<button type="button" @click="exportNotesToCSV()"
class="bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-700 px-3 py-1.5 rounded-lg text-xs font-semibold transition-colors flex items-center gap-1.5 opacity-80 hover:opacity-100"
title="Export notes">
<x-ri-download-line class="size-3.5" />
Export
</button>
</div>
<!-- Export Error Modal -->
<div x-show="showExportErrorModal"
x-transition:enter="transition ease-out duration-300"
x-transition:enter="transition ease-out duration-200"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@click="showExportErrorModal = false"
@@ -881,58 +996,69 @@
style="display: none;">
</div>
<div x-show="showExportErrorModal" @click.away="showExportErrorModal = false"
x-transition:enter="transition ease-out duration-200 translate-y-4"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-150"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 z-[60] flex items-center justify-center p-4" style="display: none;">
<div class="relative w-full max-w-sm bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 shadow-xl p-4">
<button @click="showExportErrorModal = false" class="absolute top-3 right-3 text-gray-400 hover:text-gray-600 dark:hover:text-gray-300">
<x-ri-close-line class="size-4" />
<div class="relative w-full max-w-sm bg-white dark:bg-gray-900 rounded-2xl border border-gray-200 dark:border-gray-800 shadow-2xl p-6">
<button @click="showExportErrorModal = false" class="absolute top-4 right-4 text-gray-400 opacity-75 hover:opacity-100 transition-opacity">
<x-ri-close-line class="size-5" />
</button>
<div class="flex items-center gap-2 mb-3 pr-6">
<div class="flex items-center justify-center w-8 h-8 rounded bg-red-50 dark:bg-red-900">
<x-ri-error-warning-line class="size-4 text-error" />
<div class="flex items-start gap-3 mb-4 pr-8">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-error/15 flex-shrink-0">
<x-ri-alert-line class="size-5 text-error opacity-80" />
</div>
<div>
<h6 class="text-base font-bold text-gray-900 dark:text-white">Export Error</h6>
<p class="text-xs text-gray-600 dark:text-gray-400 opacity-80 mt-1">Unable to export notes</p>
</div>
<h6 class="text-base font-semibold">Export Error</h6>
</div>
<p class="text-sm text-gray-700 dark:text-gray-300 mb-3">No notes available to export.</p>
<p class="text-sm text-gray-700 dark:text-gray-300 opacity-90 mb-5">No notes available to export. Add some notes first.</p>
<div class="flex justify-end">
<x-proxmox::ui.button type="button" @click="showExportErrorModal = false" variant="secondary">Close</x-proxmox::ui.button>
<button type="button" @click="showExportErrorModal = false" class="bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 px-3 py-1.5 rounded-lg text-sm font-semibold transition-colors">Close</button>
</div>
</div>
</div>
<div class="flex flex-col gap-2 flex-1">
<div class="flex flex-col gap-3 flex-1">
@if($isEditingNotes)
<div class="space-y-2 flex-1 flex flex-col">
<div class="space-y-3 flex-1 flex flex-col">
<textarea wire:model="notes"
class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded px-2 py-1 text-sm w-full resize-y flex-1 focus:outline-none focus:ring-2 focus:ring-secondary"
placeholder="Enter notes..."></textarea>
class="bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg px-3 py-2 text-sm w-full resize-y flex-1 focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors font-mono"
placeholder="Enter notes with Markdown support..."></textarea>
@if($notesError)
<div class="text-xs text-error">{{ $notesError }}</div>
<div class="flex items-center gap-2 text-xs text-error opacity-90">
<x-ri-alert-line class="size-3.5" />
{{ $notesError }}
</div>
@endif
<div class="flex gap-2">
<x-proxmox::ui.button wire:click="saveNotes" wire:loading.attr="disabled" variant="primary">
<button wire:click="saveNotes" wire:loading.attr="disabled" class="bg-primary hover:bg-primary/90 disabled:opacity-50 text-white px-3 py-1.5 rounded-lg text-sm font-semibold transition-colors">
<span wire:loading.remove wire:target="saveNotes">Save</span>
<span wire:loading wire:target="saveNotes">Saving...</span>
</x-proxmox::ui.button>
<x-proxmox::ui.button wire:click="cancelEditingNotes" wire:loading.attr="disabled" variant="secondary">Cancel</x-proxmox::ui.button>
</button>
<button wire:click="cancelEditingNotes" wire:loading.attr="disabled" class="bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 px-3 py-1.5 rounded-lg text-sm font-semibold transition-colors">Cancel</button>
</div>
</div>
@else
<div class="space-y-2 flex-1 flex flex-col">
<div class="bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded px-2 py-1 text-sm flex-1 overflow-auto prose prose-sm dark:prose-invert max-w-none text-gray-700 dark:text-gray-300">
<div class="space-y-3 flex-1 flex flex-col">
<div class="bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg px-3 py-2.5 text-sm flex-1 overflow-auto prose prose-sm dark:prose-invert max-w-none text-gray-700 dark:text-gray-300 leading-relaxed">
@if($notes)
{!! \Illuminate\Support\Str::markdown(e($notes), [
'allow_unsafe_links' => false,
'renderer' => ['soft_break' => "<br>"]
]) !!}
@else
<p class="text-gray-500 dark:text-gray-400">No notes. Click Edit to add.</p>
<p class="text-gray-500 dark:text-gray-400 opacity-80">No notes yet. Click Edit to add some.</p>
@endif
</div>
<div class="flex gap-2">
<x-proxmox::ui.button wire:click="startEditingNotes" variant="secondary" icon="ri-edit-fill">
Edit
</x-proxmox::ui.button>
</div>
<button wire:click="startEditingNotes" class="bg-primary hover:bg-primary/90 text-white px-3 py-1.5 rounded-lg text-sm font-semibold transition-colors inline-flex items-center gap-1.5 w-fit">
<x-ri-edit-line class="size-4" />
Edit
</button>
</div>
@endif
</div>