refactor: update resource allocation and server details layout for improved styling and usability
This commit is contained in:
@@ -311,293 +311,358 @@
|
||||
$lastSampleTimestamp = now()->toIso8601String();
|
||||
@endphp
|
||||
|
||||
<x-proxmox::ui.card class="mt-4">
|
||||
<div class="flex flex-col gap-1 md:flex-row md:items-center md:justify-between mb-4">
|
||||
<div class="flex items-center gap-2">
|
||||
<h3 class="text-base font-semibold text-gray-800 dark:text-gray-100">Resource Allocation</h3>
|
||||
<x-proxmox::ui.help-modal title="Resource Allocation">
|
||||
<p class="text-sm text-gray-700 dark:text-gray-300 mb-3">
|
||||
VM resource consumption vs allocated entitlements.
|
||||
</p>
|
||||
<div class="pt-3 border-t border-gray-200 dark:border-gray-700 space-y-1.5 text-xs text-gray-600 dark:text-gray-400">
|
||||
<p><span class="font-semibold text-gray-700 dark:text-gray-300">CPU:</span> Live usage % of vCPU entitlement.</p>
|
||||
<p><span class="font-semibold text-gray-700 dark:text-gray-300">Memory:</span> RAM usage vs provisioned.</p>
|
||||
<p><span class="font-semibold text-gray-700 dark:text-gray-300">Uptime:</span> Live uptime + power events.</p>
|
||||
<p><span class="font-semibold text-gray-700 dark:text-gray-300">Badges:</span> Healthy (green), Watchlist (yellow 85%+), Over (red).</p>
|
||||
<!-- Resource Allocation Cards -->
|
||||
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 1.25rem; margin-top: 1.5rem;">
|
||||
@foreach($resourceAllocations as $resource)
|
||||
<div class="lte-card" style="margin-bottom: 0; overflow: hidden;">
|
||||
<div class="lte-card-body" style="padding: 1.5rem;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 1rem;">
|
||||
<h5 style="font-size: 0.875rem; font-weight: 600; margin: 0; text-transform: uppercase; letter-spacing: 0.05em;" class="lte-text-muted">{{ $resource['label'] }}</h5>
|
||||
@if(isset($resource['badge']))
|
||||
<span class="lte-badge lte-badge-{{ $resource['badge']['label'] === 'Healthy' ? 'success' : ($resource['badge']['label'] === 'Watchlist' ? 'warning' : 'danger') }}" style="font-size: 0.7rem; padding: 0.25rem 0.5rem;">
|
||||
{{ $resource['badge']['label'] }}
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<div style="font-size: 1.75rem; font-weight: 700; line-height: 1; margin-bottom: 0.25rem;">
|
||||
{{ $resource['usage_summary'] }}
|
||||
</div>
|
||||
</x-proxmox::ui.help-modal>
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400 ml-2 hidden md:inline">Live refresh</span>
|
||||
<p class="lte-text-muted" style="font-size: 0.75rem; margin: 0;">{{ $resource['entitlement_label'] }}</p>
|
||||
</div>
|
||||
|
||||
@if($resource['used_percent'] !== null)
|
||||
<div style="margin-bottom: 0.75rem;">
|
||||
<div class="lte-progress" style="height: 8px; border-radius: 10px;">
|
||||
<div class="lte-progress-bar" role="progressbar"
|
||||
style="width: {{ min(100, $resource['used_percent']) }}%; background: {{ $resource['gradient'] }}; border-radius: 10px;"
|
||||
aria-valuenow="{{ $resource['used_percent'] }}"
|
||||
aria-valuemin="0"
|
||||
aria-valuemax="100">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; justify-content: space-between; margin-top: 0.375rem;">
|
||||
<span class="lte-text-muted" style="font-size: 0.75rem;">{{ number_format($resource['used_percent'], 1) }}% used</span>
|
||||
@if($resource['used_percent'] > 100)
|
||||
<span class="lte-text-danger" style="font-size: 0.75rem; font-weight: 600;">Over entitlement</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<p class="lte-text-muted" style="font-size: 0.75rem; margin: 0; line-height: 1.4;">
|
||||
<x-ri-information-line class="size-3" style="display: inline; vertical-align: text-top; margin-right: 0.25rem;" />
|
||||
{{ $resource['description'] }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-3">
|
||||
@foreach($resourceAllocations as $resource)
|
||||
<x-proxmox::ui.allocation-card
|
||||
:label="$resource['label']"
|
||||
:percent="$resource['used_percent']"
|
||||
:usage-summary="$resource['usage_summary']"
|
||||
:entitlement-label="$resource['entitlement_label']"
|
||||
:description="$resource['description']"
|
||||
:gradient="$resource['gradient']"
|
||||
:badge="$resource['badge']"
|
||||
/>
|
||||
@endforeach
|
||||
</div>
|
||||
</x-proxmox::ui.card>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mt-4 md:items-stretch">
|
||||
|
||||
<div class="grid gap-4 items-start">
|
||||
<!-- Details -->
|
||||
<div>
|
||||
|
||||
<div>
|
||||
<div style="background-image: url('https://billing.virtnet.bond/assets/images/main.png'); background-size: cover; height: 3rem;" class="rounded-t-lg border-t border-x border-gray-200 dark:border-gray-700"></div>
|
||||
|
||||
<!-- Status Section -->
|
||||
<div class="bg-white dark:bg-gray-800 border-x border-gray-200 dark:border-gray-700 p-3 space-y-3">
|
||||
<!-- Status -->
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">Status</label>
|
||||
<div class="flex items-center gap-1.5 text-sm">
|
||||
<div class="relative inline-flex size-4">
|
||||
@if ($status && $status->status === 'running')
|
||||
<span class="absolute inset-0 rounded-full bg-green-500 animate-ping opacity-75"></span>
|
||||
<span class="relative flex items-center justify-center size-4 rounded-full text-green-600 bg-green-100 dark:bg-green-900">
|
||||
<x-ri-circle-fill class="size-2" />
|
||||
</span>
|
||||
@else
|
||||
<span class="flex items-center justify-center size-4 rounded-full text-amber-600 bg-amber-100 dark:bg-amber-900">
|
||||
<x-ri-error-warning-fill class="size-2" />
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
<span class="font-medium text-gray-800 dark:text-gray-200">
|
||||
{{ $status && $status->status === 'running' ? 'Running' : 'Stopped' }}
|
||||
<!-- Main Content Grid -->
|
||||
<div style="display: grid; grid-template-columns: 1fr; gap: 1.5rem; margin-top: 1.5rem;">
|
||||
@media (min-width: 768px) {
|
||||
<style>
|
||||
.overview-grid { grid-template-columns: 1fr 1fr; }
|
||||
</style>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="overview-grid" style="display: grid; grid-template-columns: 1fr; gap: 1.5rem; margin-top: 1.5rem;">
|
||||
<!-- Server Details Card -->
|
||||
<div class="lte-card lte-card-outline-primary">
|
||||
<div class="lte-card-header" style="background: linear-gradient(135deg, var(--lte-primary), var(--lte-info)); color: white; padding: 1rem 1.25rem;">
|
||||
<div class="lte-card-title" style="display: flex; align-items: center; gap: 0.75rem; margin: 0; font-weight: 600;">
|
||||
<x-ri-server-fill class="size-5" />
|
||||
<span>Server Information</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lte-card-body" style="padding: 0;">
|
||||
<div style="background-image: url('https://billing.virtnet.bond/assets/images/main.png'); background-size: cover; height: 100px; border-bottom: 1px solid rgba(0,0,0,0.1);"></div>
|
||||
|
||||
<div style="padding: 1.25rem;">
|
||||
<!-- Status -->
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 0; border-bottom: 1px solid rgba(0,0,0,0.05);">
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<x-ri-pulse-line class="size-4 lte-text-muted" />
|
||||
<span style="font-size: 0.875rem; font-weight: 600;" class="lte-text-muted">Status</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
@if ($status && $status->status === 'running')
|
||||
<span class="lte-badge lte-badge-success" style="display: flex; align-items: center; gap: 0.375rem; padding: 0.375rem 0.75rem;">
|
||||
<span style="width: 6px; height: 6px; background: currentColor; border-radius: 50%; animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;"></span>
|
||||
Running
|
||||
</span>
|
||||
</div>
|
||||
@else
|
||||
<span class="lte-badge lte-badge-warning" style="display: flex; align-items: center; gap: 0.375rem; padding: 0.375rem 0.75rem;">
|
||||
<x-ri-stop-circle-line class="size-3" />
|
||||
Stopped
|
||||
</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Operating System -->
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 0; border-bottom: 1px solid rgba(0,0,0,0.05);">
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<x-ri-ubuntu-line class="size-4 lte-text-muted" />
|
||||
<span style="font-size: 0.875rem; font-weight: 600;" class="lte-text-muted">Operating System</span>
|
||||
</div>
|
||||
<span style="font-size: 0.875rem; font-weight: 500;">{{ $server->os->name }}</span>
|
||||
</div>
|
||||
|
||||
<!-- Hostname -->
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 0; border-bottom: 1px solid rgba(0,0,0,0.05);" x-data="{ showHostnameEditModal: @entangle('isEditingHostname') }">
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<x-ri-computer-line class="size-4 lte-text-muted" />
|
||||
<span style="font-size: 0.875rem; font-weight: 600;" class="lte-text-muted">Hostname</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<code style="font-size: 0.875rem; background: rgba(0,0,0,0.05); padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-family: 'Courier New', monospace;">{{ $server->hostname ?? $status->name ?? 'N/A' }}</code>
|
||||
<button wire:click="startEditingHostname" type="button" class="lte-btn lte-btn-xs lte-btn-outline-secondary">
|
||||
<x-ri-edit-fill class="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Operating System -->
|
||||
<div class="flex items-center justify-between">
|
||||
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">OS</label>
|
||||
<span class="text-sm text-gray-800 dark:text-gray-200">{{ $server->os->name }}</span>
|
||||
<!-- Hostname Edit Modal (unchanged for brevity) -->
|
||||
<div x-cloak x-show="showHostnameEditModal"
|
||||
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-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click="showHostnameEditModal = false; $wire.cancelEditingHostname()"
|
||||
@keydown.escape.window="showHostnameEditModal = false; $wire.cancelEditingHostname()"
|
||||
class="fixed inset-0 z-[60] bg-gray-900 bg-opacity-50"
|
||||
style="display: none;">
|
||||
</div>
|
||||
|
||||
<!-- Hostname -->
|
||||
<div class="flex items-center justify-between" x-data="{ showHostnameEditModal: @entangle('isEditingHostname') }">
|
||||
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">Hostname</label>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-sm font-mono text-gray-800 dark:text-gray-200">{{ $server->hostname ?? $status->name ?? 'N/A' }}</span>
|
||||
<button wire:click="startEditingHostname" type="button" class="text-gray-500 hover:text-gray-700 dark:text-gray-400">
|
||||
<x-ri-edit-fill class="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Hostname Edit Modal -->
|
||||
<div x-cloak x-show="showHostnameEditModal"
|
||||
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-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click="showHostnameEditModal = false; $wire.cancelEditingHostname()"
|
||||
@keydown.escape.window="showHostnameEditModal = false; $wire.cancelEditingHostname()"
|
||||
class="fixed inset-0 z-[60] bg-gray-900 bg-opacity-50"
|
||||
style="display: none;">
|
||||
</div>
|
||||
<div x-cloak x-show="showHostnameEditModal"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
@click.away="showHostnameEditModal = false; $wire.cancelEditingHostname()"
|
||||
class="fixed inset-0 z-[60] flex items-center justify-center p-4"
|
||||
style="display: none;">
|
||||
<div class="relative bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-xl w-full max-w-md p-4 space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="text-sm font-semibold text-gray-800 dark:text-gray-100">Edit Hostname</h4>
|
||||
<button type="button" class="text-gray-400 hover:text-gray-600 p-1"
|
||||
<div x-cloak x-show="showHostnameEditModal"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
@click.away="showHostnameEditModal = false; $wire.cancelEditingHostname()"
|
||||
class="fixed inset-0 z-[60] flex items-center justify-center p-4"
|
||||
style="display: none;">
|
||||
<div class="lte-card" style="width: 100%; max-width: 500px; margin: 0;">
|
||||
<div class="lte-card-header lte-bg-primary" style="color: white;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||||
<h5 class="lte-card-title" style="margin: 0; color: white;">Edit Hostname</h5>
|
||||
<button type="button" class="lte-btn lte-btn-sm" style="background: rgba(255,255,255,0.2); border: none; color: white;"
|
||||
@click="showHostnameEditModal = false; $wire.cancelEditingHostname()">
|
||||
<x-ri-close-line class="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div>
|
||||
<label class="text-xs font-medium text-gray-600 dark:text-gray-400">Hostname</label>
|
||||
<input type="text" wire:model.defer="hostname" class="form-input w-full mt-1 text-sm" placeholder="Enter hostname" />
|
||||
</div>
|
||||
@if($hostnameError)
|
||||
<div class="text-xs text-red-600">{{ $hostnameError }}</div>
|
||||
@endif
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
<strong>Save:</strong> Database only. <strong>Save & Apply:</strong> + cloud-init on next boot.
|
||||
</p>
|
||||
</div>
|
||||
<div class="lte-card-body">
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label style="font-size: 0.875rem; font-weight: 600; display: block; margin-bottom: 0.5rem;">Hostname</label>
|
||||
<input type="text" wire:model.defer="hostname" class="lte-form-control" placeholder="Enter hostname" />
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<x-proxmox::ui.button type="button" @click="showHostnameEditModal = false; $wire.cancelEditingHostname()" variant="secondary">
|
||||
Cancel
|
||||
</x-proxmox::ui.button>
|
||||
<x-proxmox::ui.button type="button" wire:click="updateHostname" variant="primary">
|
||||
Save
|
||||
</x-proxmox::ui.button>
|
||||
<x-proxmox::ui.button type="button" wire:click="applyHostname" variant="primary">
|
||||
Save & Apply
|
||||
</x-proxmox::ui.button>
|
||||
@if($hostnameError)
|
||||
<div class="lte-alert lte-alert-danger" style="font-size: 0.875rem;">{{ $hostnameError }}</div>
|
||||
@endif
|
||||
<div class="lte-callout lte-callout-info" style="font-size: 0.875rem; padding: 0.75rem;">
|
||||
<strong>Save:</strong> Database only. <strong>Save & Apply:</strong> + cloud-init on next boot.
|
||||
</div>
|
||||
</div>
|
||||
<div class="lte-card-footer" style="display: flex; justify-content: flex-end; gap: 0.5rem;">
|
||||
<button type="button" @click="showHostnameEditModal = false; $wire.cancelEditingHostname()" class="lte-btn lte-btn-secondary">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" wire:click="updateHostname" class="lte-btn lte-btn-primary">
|
||||
Save
|
||||
</button>
|
||||
<button type="button" wire:click="applyHostname" class="lte-btn lte-btn-success">
|
||||
<x-ri-check-line class="size-4" />
|
||||
Save & Apply
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Primary IPv4 Address -->
|
||||
@if($server->primaryIpv4)
|
||||
<div class="flex items-center justify-between" x-data="{ copied: false, copyText: '{{ $server->primaryIpv4->ip }}', copy() { navigator.clipboard.writeText(this.copyText); this.copied = true; setTimeout(() => this.copied = false, 2000); } }">
|
||||
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">IPv4</label>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-sm font-mono text-gray-800 dark:text-gray-200">{{ $server->primaryIpv4->ip }}</span>
|
||||
<button @click="copy()" type="button" class="text-gray-500 hover:text-gray-700 dark:text-gray-400">
|
||||
<x-ri-check-fill x-show="copied" class="size-3.5 text-green-600" />
|
||||
<x-ri-file-copy-fill x-show="!copied" class="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<!-- Primary IPv4 Address -->
|
||||
@if($server->primaryIpv4)
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 0; border-bottom: 1px solid rgba(0,0,0,0.05);" x-data="{ copied: false, copyText: '{{ $server->primaryIpv4->ip }}', copy() { navigator.clipboard.writeText(this.copyText); this.copied = true; setTimeout(() => this.copied = false, 2000); } }">
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<x-ri-global-line class="size-4 lte-text-muted" />
|
||||
<span style="font-size: 0.875rem; font-weight: 600;" class="lte-text-muted">IPv4 Address</span>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Primary IPv6 Address -->
|
||||
@if($server->primaryIpv6)
|
||||
<div class="flex items-center justify-between" x-data="{ copied: false, copyText: '{{ $server->primaryIpv6->ip }}', copy() { navigator.clipboard.writeText(this.copyText); this.copied = true; setTimeout(() => this.copied = false, 2000); } }">
|
||||
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">IPv6</label>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-xs font-mono text-gray-800 dark:text-gray-200 truncate max-w-[180px]">{{ $server->primaryIpv6->ip }}</span>
|
||||
<button @click="copy()" type="button" class="text-gray-500 hover:text-gray-700 dark:text-gray-400">
|
||||
<x-ri-check-fill x-show="copied" class="size-3.5 text-green-600" />
|
||||
<x-ri-file-copy-fill x-show="!copied" class="size-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<code style="font-size: 0.875rem; background: rgba(0,0,0,0.05); padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-family: 'Courier New', monospace;">{{ $server->primaryIpv4->ip }}</code>
|
||||
<button @click="copy()" type="button" class="lte-btn lte-btn-xs lte-btn-outline-secondary">
|
||||
<x-ri-check-fill x-show="copied" class="size-3 lte-text-success" />
|
||||
<x-ri-file-copy-fill x-show="!copied" class="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Root Password -->
|
||||
<div class="flex items-center justify-between" x-data="{ showPasswordViewModal: false, showPasswordEditModal: @entangle('isEditingPassword') }">
|
||||
<label class="text-xs font-semibold text-gray-600 dark:text-gray-400">Password</label>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<span class="text-sm text-gray-800 dark:text-gray-200">
|
||||
@if(isset($server->password) && $server->password)
|
||||
••••••••
|
||||
@else
|
||||
Emailed
|
||||
@endif
|
||||
</span>
|
||||
<!-- Primary IPv6 Address -->
|
||||
@if($server->primaryIpv6)
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 0; border-bottom: 1px solid rgba(0,0,0,0.05);" x-data="{ copied: false, copyText: '{{ $server->primaryIpv6->ip }}', copy() { navigator.clipboard.writeText(this.copyText); this.copied = true; setTimeout(() => this.copied = false, 2000); } }">
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<x-ri-global-line class="size-4 lte-text-muted" />
|
||||
<span style="font-size: 0.875rem; font-weight: 600;" class="lte-text-muted">IPv6 Address</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<code style="font-size: 0.75rem; background: rgba(0,0,0,0.05); padding: 0.25rem 0.5rem; border-radius: 0.25rem; font-family: 'Courier New', monospace; max-width: 200px; overflow: hidden; text-overflow: ellipsis;">{{ $server->primaryIpv6->ip }}</code>
|
||||
<button @click="copy()" type="button" class="lte-btn lte-btn-xs lte-btn-outline-secondary">
|
||||
<x-ri-check-fill x-show="copied" class="size-3 lte-text-success" />
|
||||
<x-ri-file-copy-fill x-show="!copied" class="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Root Password -->
|
||||
<div style="display: flex; align-items: center; justify-content: space-between; padding: 0.75rem 0;" x-data="{ showPasswordViewModal: false, showPasswordEditModal: @entangle('isEditingPassword') }">
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<x-ri-lock-password-line class="size-4 lte-text-muted" />
|
||||
<span style="font-size: 0.875rem; font-weight: 600;" class="lte-text-muted">Root Password</span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<span style="font-size: 0.875rem;">
|
||||
@if(isset($server->password) && $server->password)
|
||||
<button type="button" class="text-gray-500 hover:text-gray-700 dark:text-gray-400" @click="showPasswordViewModal = true">
|
||||
<x-ri-eye-line class="size-3.5" />
|
||||
</button>
|
||||
••••••••
|
||||
@else
|
||||
<span class="lte-badge lte-badge-info">Emailed</span>
|
||||
@endif
|
||||
<button type="button" class="text-gray-500 hover:text-gray-700 dark:text-gray-400" wire:click="startEditingPassword">
|
||||
<x-ri-edit-fill class="size-3.5" />
|
||||
</span>
|
||||
@if(isset($server->password) && $server->password)
|
||||
<button type="button" class="lte-btn lte-btn-xs lte-btn-outline-secondary" @click="showPasswordViewModal = true">
|
||||
<x-ri-eye-line class="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
@endif
|
||||
<button type="button" class="lte-btn lte-btn-xs lte-btn-outline-primary" wire:click="startEditingPassword">
|
||||
<x-ri-edit-fill class="size-3" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Password View Modal -->
|
||||
<div x-cloak x-show="showPasswordViewModal"
|
||||
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-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click="showPasswordViewModal = false"
|
||||
@keydown.escape.window="showPasswordViewModal = false"
|
||||
class="fixed inset-0 z-[60] bg-gray-900 bg-opacity-50"
|
||||
style="display: none;">
|
||||
</div>
|
||||
<div x-cloak x-show="showPasswordViewModal"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
@click.away="showPasswordViewModal = false"
|
||||
class="fixed inset-0 z-[60] flex items-center justify-center p-4"
|
||||
style="display: none;"
|
||||
x-data="{
|
||||
copied: false,
|
||||
copyText: {{ json_encode($server->password) }},
|
||||
copy() { navigator.clipboard.writeText(this.copyText); this.copied = true; setTimeout(() => this.copied = false, 2000); }
|
||||
}">
|
||||
<div class="relative bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-xl w-full max-w-md p-4 space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="text-sm font-semibold text-gray-800 dark:text-gray-100">Root Password</h4>
|
||||
<button type="button" class="text-gray-400 hover:text-gray-600 p-1" @click="showPasswordViewModal = false">
|
||||
<!-- Password View Modal -->
|
||||
<div x-cloak x-show="showPasswordViewModal"
|
||||
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-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click="showPasswordViewModal = false"
|
||||
@keydown.escape.window="showPasswordViewModal = false"
|
||||
class="fixed inset-0 z-[60] bg-gray-900 bg-opacity-50"
|
||||
style="display: none;">
|
||||
</div>
|
||||
<div x-cloak x-show="showPasswordViewModal"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
@click.away="showPasswordViewModal = false"
|
||||
class="fixed inset-0 z-[60] flex items-center justify-center p-4"
|
||||
style="display: none;"
|
||||
x-data="{
|
||||
copied: false,
|
||||
copyText: {{ json_encode($server->password) }},
|
||||
copy() { navigator.clipboard.writeText(this.copyText); this.copied = true; setTimeout(() => this.copied = false, 2000); }
|
||||
}">
|
||||
<div class="lte-card" style="width: 100%; max-width: 500px; margin: 0;">
|
||||
<div class="lte-card-header lte-bg-primary" style="color: white;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||||
<h5 class="lte-card-title" style="margin: 0; color: white;">Root Password</h5>
|
||||
<button type="button" class="lte-btn lte-btn-sm" style="background: rgba(255,255,255,0.2); border: none; color: white;" @click="showPasswordViewModal = false">
|
||||
<x-ri-close-line class="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="bg-gray-100 dark:bg-gray-900 border border-gray-200 dark:border-gray-700 rounded p-2 text-sm text-gray-800 dark:text-gray-200 overflow-auto">
|
||||
<code class="break-all block font-mono text-xs">{{ $server->password }}</code>
|
||||
</div>
|
||||
<div class="flex justify-end">
|
||||
<button type="button" class="px-3 py-1.5 rounded bg-blue-600 text-white text-xs font-medium hover:bg-blue-700" @click="copy()">
|
||||
<span x-show="!copied">Copy</span>
|
||||
<span x-show="copied">Copied!</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="lte-card-body">
|
||||
<div style="background: rgba(0,0,0,0.05); border: 1px solid rgba(0,0,0,0.1); border-radius: 0.375rem; padding: 1rem;">
|
||||
<code style="display: block; font-family: 'Courier New', monospace; font-size: 0.875rem; word-break: break-all;">{{ $server->password }}</code>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lte-card-footer" style="display: flex; justify-content: flex-end;">
|
||||
<button type="button" class="lte-btn lte-btn-success" @click="copy()">
|
||||
<x-ri-file-copy-line class="size-4" />
|
||||
<span x-show="!copied">Copy Password</span>
|
||||
<span x-show="copied">Copied!</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password Edit Modal -->
|
||||
<div x-cloak x-show="showPasswordEditModal"
|
||||
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-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click="showPasswordEditModal = false; $wire.cancelEditingPassword()"
|
||||
@keydown.escape.window="showPasswordEditModal = false; $wire.cancelEditingPassword()"
|
||||
class="fixed inset-0 z-[60] bg-gray-900 bg-opacity-50"
|
||||
style="display: none;">
|
||||
</div>
|
||||
<div x-cloak x-show="showPasswordEditModal"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
@click.away="showPasswordEditModal = false; $wire.cancelEditingPassword()"
|
||||
class="fixed inset-0 z-[60] flex items-center justify-center p-4"
|
||||
style="display: none;">
|
||||
<div class="relative bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg shadow-xl w-full max-w-md p-4 space-y-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<h4 class="text-sm font-semibold text-gray-800 dark:text-gray-100">Edit Root Password</h4>
|
||||
<button type="button" class="text-gray-400 hover:text-gray-600 p-1"
|
||||
<!-- Password Edit Modal (keeping structure but styling with AdminLTE) -->
|
||||
<div x-cloak x-show="showPasswordEditModal"
|
||||
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-150"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
@click="showPasswordEditModal = false; $wire.cancelEditingPassword()"
|
||||
@keydown.escape.window="showPasswordEditModal = false; $wire.cancelEditingPassword()"
|
||||
class="fixed inset-0 z-[60] bg-gray-900 bg-opacity-50"
|
||||
style="display: none;">
|
||||
</div>
|
||||
<div x-cloak x-show="showPasswordEditModal"
|
||||
x-transition:enter="transition ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 scale-95"
|
||||
x-transition:enter-end="opacity-100 scale-100"
|
||||
x-transition:leave="transition ease-in duration-150"
|
||||
x-transition:leave-start="opacity-100 scale-100"
|
||||
x-transition:leave-end="opacity-0 scale-95"
|
||||
@click.away="showPasswordEditModal = false; $wire.cancelEditingPassword()"
|
||||
class="fixed inset-0 z-[60] flex items-center justify-center p-4"
|
||||
style="display: none;">
|
||||
<div class="lte-card" style="width: 100%; max-width: 500px; margin: 0;">
|
||||
<div class="lte-card-header lte-bg-primary" style="color: white;">
|
||||
<div style="display: flex; align-items: center; justify-content: space-between;">
|
||||
<h5 class="lte-card-title" style="margin: 0; color: white;">Edit Root Password</h5>
|
||||
<button type="button" class="lte-btn lte-btn-sm" style="background: rgba(255,255,255,0.2); border: none; color: white;"
|
||||
@click="showPasswordEditModal = false; $wire.cancelEditingPassword()">
|
||||
<x-ri-close-line class="size-4" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="space-y-2">
|
||||
<div>
|
||||
<label class="text-xs font-medium text-gray-600 dark:text-gray-400">New Password</label>
|
||||
<input type="password" wire:model.defer="password" class="form-input w-full mt-1 text-sm" placeholder="Enter new password" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="text-xs font-medium text-gray-600 dark:text-gray-400">Confirm Password</label>
|
||||
<input type="password"
|
||||
wire:model.defer="password_confirmation"
|
||||
class="form-input w-full mt-1 text-sm"
|
||||
placeholder="Confirm password" />
|
||||
</div>
|
||||
@if($passwordError)
|
||||
<div class="text-xs text-red-600">{{ $passwordError }}</div>
|
||||
@endif
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||
<strong>Save:</strong> Database only. <strong>Save & Apply:</strong> + cloud-init (reboot required).
|
||||
</p>
|
||||
</div>
|
||||
<div class="lte-card-body">
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label style="font-size: 0.875rem; font-weight: 600; display: block; margin-bottom: 0.5rem;">New Password</label>
|
||||
<input type="password" wire:model.defer="password" class="lte-form-control" placeholder="Enter new password" />
|
||||
</div>
|
||||
<div class="flex justify-end gap-2">
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label style="font-size: 0.875rem; font-weight: 600; display: block; margin-bottom: 0.5rem;">Confirm Password</label>
|
||||
<input type="password" wire:model.defer="password_confirmation" class="lte-form-control" placeholder="Confirm password" />
|
||||
</div>
|
||||
@if($passwordError)
|
||||
<div class="lte-alert lte-alert-danger" style="font-size: 0.875rem; margin-bottom: 1rem;">{{ $passwordError }}</div>
|
||||
@endif
|
||||
<div class="lte-callout lte-callout-info" style="font-size: 0.875rem; padding: 0.75rem;">
|
||||
<strong>Save:</strong> Database only. <strong>Save & Apply:</strong> + cloud-init (reboot required).
|
||||
</div>
|
||||
</div>
|
||||
<div class="lte-card-footer" style="display: flex; justify-content: flex-end; gap: 0.5rem;">
|
||||
<button type="button" @click="showPasswordEditModal = false; $wire.cancelEditingPassword()" class="lte-btn lte-btn-secondary">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" wire:click="updatePassword" class="lte-btn lte-btn-primary">
|
||||
Save
|
||||
</button>
|
||||
<button type="button" wire:click="applyPassword" class="lte-btn lte-btn-success">
|
||||
<x-ri-check-line class="size-4" />
|
||||
Save & Apply
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<x-proxmox::ui.button type="button" @click="showPasswordEditModal = false; $wire.cancelEditingPassword()" variant="secondary">
|
||||
Cancel
|
||||
</x-proxmox::ui.button>
|
||||
|
||||
Reference in New Issue
Block a user