556 lines
38 KiB
PHP
556 lines
38 KiB
PHP
<x-proxmox::layout
|
|
:server="$server"
|
|
view="overview"
|
|
:status="$status"
|
|
:status-polling="true"
|
|
:status-polling-interval="'2s'"
|
|
>
|
|
@if(!$status)
|
|
<x-proxmox::ui.alert variant="danger" icon="ri-error-warning-fill" class="mt-8">
|
|
<x-slot name="title">Unable to Load Server Status</x-slot>
|
|
<p class="text-gray-600 dark:text-gray-400 mb-3">Could not retrieve status from hypervisor:</p>
|
|
<ul class="list-disc list-inside text-gray-600 dark:text-gray-400 mb-3 space-y-1 text-sm">
|
|
<li>Temporary connectivity issues with the Proxmox cluster</li>
|
|
<li>The VM may be in an intermediate state</li>
|
|
<li>Network timeout while fetching data</li>
|
|
</ul>
|
|
<x-proxmox::ui.button wire:click="loadVmStatus" icon="ri-refresh-line">
|
|
Retry
|
|
</x-proxmox::ui.button>
|
|
</x-proxmox::ui.alert>
|
|
@else
|
|
|
|
@php
|
|
// Resource calculations
|
|
$cpuCores = max(1, (int)($status->cpus ?? 1));
|
|
$cpuUsagePercent = round(max(0, ($status->cpu ?? 0) * 100), 1);
|
|
$memoryTotal = (int)($status->maxmem ?? 0);
|
|
$memoryUsed = (int)($status->mem ?? 0);
|
|
$memoryPercent = $memoryTotal > 0 ? round(($memoryUsed / $memoryTotal) * 100, 1) : 0;
|
|
$diskTotal = (int)($status->maxdisk ?? 0);
|
|
$diskUsed = (int)($status->disk ?? 0);
|
|
$diskPercent = $diskTotal > 0 ? round(($diskUsed / $diskTotal) * 100, 1) : 0;
|
|
|
|
// Uptime
|
|
$reportedUptime = $this->status && isset($this->status->uptime)
|
|
? $this->formatSeconds($this->status->uptime)
|
|
: 'N/A';
|
|
|
|
// Power stats
|
|
$powerStats = $this->powerEventStats ?? ['starts' => 0, 'shutdowns' => 0, 'reboots' => 0];
|
|
|
|
// Network totals
|
|
$totalNetIn = (int)($status->netin ?? 0);
|
|
$totalNetOut = (int)($status->netout ?? 0);
|
|
|
|
// Product/Service info
|
|
$productSettings = $server->service?->product?->settings ?? collect();
|
|
$allocatedCores = $productSettings->where('key', 'cores')->first()?->value ?? $cpuCores;
|
|
$allocatedMemoryMiB = $productSettings->where('key', 'memory')->first()?->value ?? 0;
|
|
$allocatedDiskGiB = $productSettings->where('key', 'disk')->first()?->value ?? 0;
|
|
$allocatedBackups = $productSettings->where('key', 'backups')->first()?->value ?? 0;
|
|
$serviceExpires = $server->service?->expires_at;
|
|
$serviceStatus = $server->service?->status ?? 'unknown';
|
|
$productName = $server->service?->product?->name ?? 'Unknown Product';
|
|
$locationName = $server->node?->location?->name ?? 'Unknown';
|
|
$nodeName = $server->node?->name ?? 'Unknown';
|
|
|
|
// Badge helper
|
|
$getBadge = function($percent) {
|
|
if ($percent >= 90) return ['label' => 'Critical', 'class' => 'bg-error/15 text-error'];
|
|
if ($percent >= 75) return ['label' => 'Warning', 'class' => 'bg-warning/15 text-warning'];
|
|
return ['label' => 'Healthy', 'class' => 'bg-success/15 text-success'];
|
|
};
|
|
@endphp
|
|
|
|
<div class="space-y-6" x-data="{ pageLoaded: false }" x-init="setTimeout(() => pageLoaded = true, 50)" wire:init="checkStatus" wire:poll.{{ $fastPolling ? '2s' : '30s' }}="checkStatus">
|
|
|
|
<!-- Page Header -->
|
|
<div :class="pageLoaded ? 'opacity-100 translate-y-0' : 'opacity-0 -translate-y-2'" style="transition: all 0.4s ease-out;">
|
|
<x-proxmox::ui.section-header
|
|
title="Server Overview"
|
|
icon="ri-server-line"
|
|
subtitle="Real-time monitoring and configuration for your virtual machine."
|
|
class="mb-0">
|
|
</x-proxmox::ui.section-header>
|
|
</div>
|
|
|
|
<!-- Resource Metrics -->
|
|
<section class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl p-6 shadow-sm 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.1s;">
|
|
<div class="flex items-center justify-between mb-5">
|
|
<div class="flex items-center gap-2">
|
|
<x-ri-dashboard-line class="size-5 text-primary" />
|
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Resource Metrics</h2>
|
|
</div>
|
|
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-medium rounded-full {{ $status->status === 'running' ? 'bg-success/15 text-success' : 'bg-warning/15 text-warning' }}">
|
|
@if($status->status === 'running')
|
|
<span class="w-1.5 h-1.5 bg-success rounded-full animate-pulse"></span>
|
|
Running
|
|
@else
|
|
<span class="w-1.5 h-1.5 bg-warning rounded-full"></span>
|
|
Stopped
|
|
@endif
|
|
</span>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
|
<!-- CPU -->
|
|
<div class="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-4 border border-gray-100 dark:border-gray-700/50">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<div class="flex items-center gap-2">
|
|
<div class="w-8 h-8 rounded-lg bg-blue-100 dark:bg-blue-900/50 flex items-center justify-center">
|
|
<x-ri-cpu-line class="size-4 text-blue-600 dark:text-blue-400" />
|
|
</div>
|
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">CPU</span>
|
|
</div>
|
|
<span class="text-xs font-medium px-2 py-0.5 rounded-full {{ $getBadge($cpuUsagePercent)['class'] }}">{{ $getBadge($cpuUsagePercent)['label'] }}</span>
|
|
</div>
|
|
<div class="text-2xl font-bold text-gray-900 dark:text-white mb-1">{{ $cpuUsagePercent }}%</div>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">{{ $cpuCores }} vCPU allocated</p>
|
|
<div class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
<div class="h-full bg-gradient-to-r from-blue-400 to-blue-600 rounded-full transition-all duration-500" style="width: {{ min(100, $cpuUsagePercent) }}%"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Memory -->
|
|
<div class="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-4 border border-gray-100 dark:border-gray-700/50">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<div class="flex items-center gap-2">
|
|
<div class="w-8 h-8 rounded-lg bg-emerald-100 dark:bg-emerald-900/50 flex items-center justify-center">
|
|
<x-ri-database-2-line class="size-4 text-emerald-600 dark:text-emerald-400" />
|
|
</div>
|
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Memory</span>
|
|
</div>
|
|
<span class="text-xs font-medium px-2 py-0.5 rounded-full {{ $getBadge($memoryPercent)['class'] }}">{{ $getBadge($memoryPercent)['label'] }}</span>
|
|
</div>
|
|
<div class="text-2xl font-bold text-gray-900 dark:text-white mb-1">{{ $memoryPercent }}%</div>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400 mb-3">{{ \Illuminate\Support\Number::fileSize($memoryUsed) }} / {{ \Illuminate\Support\Number::fileSize($memoryTotal) }}</p>
|
|
<div class="w-full h-2 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
<div class="h-full bg-gradient-to-r from-emerald-400 to-emerald-600 rounded-full transition-all duration-500" style="width: {{ min(100, $memoryPercent) }}%"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Uptime -->
|
|
<div class="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-4 border border-gray-100 dark:border-gray-700/50">
|
|
<div class="flex items-center justify-between mb-3">
|
|
<div class="flex items-center gap-2">
|
|
<div class="w-8 h-8 rounded-lg bg-violet-100 dark:bg-violet-900/50 flex items-center justify-center">
|
|
<x-ri-time-line class="size-4 text-violet-600 dark:text-violet-400" />
|
|
</div>
|
|
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">Uptime</span>
|
|
</div>
|
|
<span class="text-xs font-medium px-2 py-0.5 rounded-full bg-blue-100 dark:bg-blue-900/50 text-blue-600 dark:text-blue-400">Live</span>
|
|
</div>
|
|
<div class="text-2xl font-bold text-gray-900 dark:text-white mb-1">{{ $reportedUptime }}</div>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">{{ $powerStats['starts'] }} starts • {{ $powerStats['shutdowns'] }} stops • {{ $powerStats['reboots'] }} reboots</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Server Details & DNS -->
|
|
<div class="grid grid-cols-1 xl:grid-cols-2 gap-6">
|
|
|
|
<!-- Server Details -->
|
|
<section class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-sm overflow-hidden 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="flex items-center gap-2 px-5 py-4 border-b border-gray-100 dark:border-gray-800">
|
|
<x-ri-server-line class="size-5 text-primary" />
|
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Server Details</h2>
|
|
</div>
|
|
|
|
<div class="p-5 space-y-1">
|
|
<!-- Status -->
|
|
<div class="flex items-center justify-between py-2.5 px-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors">
|
|
<span class="text-sm text-gray-600 dark:text-gray-400">Status</span>
|
|
<span class="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs font-semibold rounded-full {{ $status->status === 'running' ? 'bg-success/15 text-success' : 'bg-warning/15 text-warning' }}">
|
|
@if($status->status === 'running')
|
|
<span class="w-1.5 h-1.5 bg-success rounded-full animate-pulse"></span>
|
|
@endif
|
|
{{ ucfirst($status->status) }}
|
|
</span>
|
|
</div>
|
|
|
|
<!-- OS -->
|
|
<div class="flex items-center justify-between py-2.5 px-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors">
|
|
<span class="text-sm text-gray-600 dark:text-gray-400">Operating System</span>
|
|
<span class="text-sm font-medium text-gray-900 dark:text-white">{{ $server->os->name ?? 'Unknown' }}</span>
|
|
</div>
|
|
|
|
<!-- Hostname -->
|
|
<div class="flex items-center justify-between py-2.5 px-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors">
|
|
<span class="text-sm text-gray-600 dark:text-gray-400">Hostname</span>
|
|
<div class="flex items-center gap-2">
|
|
<code class="text-sm font-mono text-gray-900 dark:text-white">{{ $server->hostname ?? $status->name ?? 'N/A' }}</code>
|
|
<button wire:click="startEditingHostname" type="button" class="text-primary/70 hover:text-primary transition-colors">
|
|
<x-ri-edit-line class="size-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- IPv4 -->
|
|
@if($server->primaryIpv4)
|
|
<div class="flex items-center justify-between py-2.5 px-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors" x-data="{ copied: false }">
|
|
<span class="text-sm text-gray-600 dark:text-gray-400">IPv4 Address</span>
|
|
<div class="flex items-center gap-2">
|
|
<code class="text-sm font-mono text-gray-900 dark:text-white">{{ $server->primaryIpv4->ip }}</code>
|
|
<button @click="navigator.clipboard.writeText('{{ $server->primaryIpv4->ip }}'); copied = true; setTimeout(() => copied = false, 2000)" type="button" class="text-gray-400 hover:text-success transition-colors">
|
|
<x-ri-check-fill x-show="copied" class="size-4 text-success" x-cloak />
|
|
<x-ri-file-copy-line x-show="!copied" class="size-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- IPv6 -->
|
|
@if($server->primaryIpv6)
|
|
<div class="flex items-center justify-between py-2.5 px-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors" x-data="{ copied: false }">
|
|
<span class="text-sm text-gray-600 dark:text-gray-400">IPv6 Address</span>
|
|
<div class="flex items-center gap-2">
|
|
<code class="text-xs font-mono text-gray-900 dark:text-white truncate max-w-[160px]">{{ $server->primaryIpv6->ip }}</code>
|
|
<button @click="navigator.clipboard.writeText('{{ $server->primaryIpv6->ip }}'); copied = true; setTimeout(() => copied = false, 2000)" type="button" class="text-gray-400 hover:text-success transition-colors flex-shrink-0">
|
|
<x-ri-check-fill x-show="copied" class="size-4 text-success" x-cloak />
|
|
<x-ri-file-copy-line x-show="!copied" class="size-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
<!-- Password -->
|
|
<div class="flex items-center justify-between py-2.5 px-3 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800/50 transition-colors">
|
|
<span class="text-sm text-gray-600 dark:text-gray-400">Root Password</span>
|
|
<div class="flex items-center gap-2">
|
|
@if($server->password)
|
|
<span class="text-sm text-gray-500">••••••••</span>
|
|
@else
|
|
<span class="text-xs font-medium px-2 py-0.5 rounded-full bg-info/15 text-info">Emailed</span>
|
|
@endif
|
|
<button wire:click="startEditingPassword" type="button" class="text-primary/70 hover:text-primary transition-colors">
|
|
<x-ri-edit-line class="size-4" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Reinstall -->
|
|
<div class="flex items-center justify-between bg-error/5 border-t border-error/20 px-5 py-3">
|
|
<div>
|
|
<span class="text-sm font-semibold text-error">Reinstall Server</span>
|
|
<p class="text-xs text-error/70">Permanently deletes all data</p>
|
|
</div>
|
|
<livewire:proxmox.reinstall :server="$server" />
|
|
</div>
|
|
</section>
|
|
|
|
<!-- DNS Configuration -->
|
|
<section class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-sm overflow-hidden 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.2s;">
|
|
<div class="flex items-center gap-2 px-5 py-4 border-b border-gray-100 dark:border-gray-800">
|
|
<x-ri-global-line class="size-5 text-primary" />
|
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">DNS Configuration</h2>
|
|
</div>
|
|
|
|
<div class="p-5 space-y-3">
|
|
@if($server->primaryIpv6)
|
|
@php
|
|
$ipv6Dashed = str_replace(':', '-', $server->primaryIpv6->ip);
|
|
$dnsOptions = [
|
|
['name' => 'Virtnet Internal', 'domain' => $ipv6Dashed . '.virtnet.host', 'type' => 'Internal'],
|
|
['name' => 'nip.io', 'domain' => $ipv6Dashed . '.nip.io', 'type' => 'Public'],
|
|
['name' => 'sslip.io', 'domain' => $ipv6Dashed . '.sslip.io', 'type' => 'Public'],
|
|
];
|
|
@endphp
|
|
|
|
@foreach($dnsOptions as $dns)
|
|
<div class="flex items-center justify-between p-3 rounded-lg bg-gray-50 dark:bg-gray-800/50 hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors" x-data="{ copied: false }">
|
|
<div class="min-w-0 flex-1">
|
|
<div class="flex items-center gap-2 mb-1">
|
|
<span class="text-xs font-semibold text-gray-600 dark:text-gray-400">{{ $dns['name'] }}</span>
|
|
<span class="text-xs px-1.5 py-0.5 rounded bg-gray-200 dark:bg-gray-700 text-gray-500 dark:text-gray-400">{{ $dns['type'] }}</span>
|
|
</div>
|
|
<code class="text-sm text-gray-900 dark:text-white font-mono break-all">{{ $dns['domain'] }}</code>
|
|
</div>
|
|
<button @click="navigator.clipboard.writeText('{{ $dns['domain'] }}'); copied = true; setTimeout(() => copied = false, 2000)" type="button" class="text-gray-400 hover:text-success transition-colors ml-3 flex-shrink-0">
|
|
<x-ri-check-fill x-show="copied" class="size-4 text-success" x-cloak />
|
|
<x-ri-file-copy-line x-show="!copied" class="size-4" />
|
|
</button>
|
|
</div>
|
|
@endforeach
|
|
@else
|
|
<div class="text-center py-8">
|
|
<x-ri-global-line class="size-10 text-gray-300 dark:text-gray-600 mx-auto mb-3" />
|
|
<p class="text-sm text-gray-500 dark:text-gray-400">No IPv6 address assigned</p>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="bg-gray-50 dark:bg-gray-800/50 border-t border-gray-100 dark:border-gray-800 px-5 py-3">
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">
|
|
<x-ri-information-line class="inline size-3.5 mr-1" />
|
|
These hostnames automatically resolve to your server's IP address.
|
|
</p>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- Notes Section -->
|
|
<section class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-sm overflow-hidden 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.25s;">
|
|
<div class="flex items-center gap-2 px-5 py-4 border-b border-gray-100 dark:border-gray-800">
|
|
<x-ri-file-text-line class="size-5 text-primary" />
|
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Notes</h2>
|
|
<span class="text-xs text-gray-500 dark:text-gray-400 ml-1">(Markdown supported)</span>
|
|
</div>
|
|
|
|
<div class="p-5">
|
|
@if($isEditingNotes)
|
|
<div class="space-y-4">
|
|
<textarea wire:model.live.debounce.500ms="notes"
|
|
class="w-full min-h-[120px] bg-gray-50 dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-lg px-4 py-3 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary/50 focus:border-primary transition-colors resize-y"
|
|
placeholder="Add notes about this server..."></textarea>
|
|
@if($notesError)
|
|
<p class="text-xs text-error flex items-center gap-1"><x-ri-alert-line class="size-3.5" /> {{ $notesError }}</p>
|
|
@endif
|
|
<div class="flex gap-2">
|
|
<button wire:click="saveNotes" class="px-4 py-2 bg-primary hover:bg-primary/90 text-white text-sm font-medium rounded-lg transition-colors">Save</button>
|
|
<button wire:click="cancelEditingNotes" class="px-4 py-2 bg-gray-100 dark:bg-gray-800 hover:bg-gray-200 dark:hover:bg-gray-700 text-sm font-medium rounded-lg transition-colors">Cancel</button>
|
|
</div>
|
|
</div>
|
|
@else
|
|
<div class="space-y-4">
|
|
<div class="bg-gray-50 dark:bg-gray-800/50 border border-gray-100 dark:border-gray-700/50 rounded-lg px-4 py-3 min-h-[80px] prose prose-sm dark:prose-invert max-w-none">
|
|
@if($notes)
|
|
{!! \Illuminate\Support\Str::markdown(e($notes), ['allow_unsafe_links' => false]) !!}
|
|
@else
|
|
<p class="text-gray-400 dark:text-gray-500 italic m-0">No notes yet. Click Edit to add some.</p>
|
|
@endif
|
|
</div>
|
|
<button wire:click="startEditingNotes" class="px-4 py-2 bg-primary hover:bg-primary/90 text-white text-sm font-medium rounded-lg transition-colors inline-flex items-center gap-2">
|
|
<x-ri-edit-line class="size-4" />
|
|
Edit Notes
|
|
</button>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Server Information Grid -->
|
|
<section class="bg-white dark:bg-gray-900 border border-gray-200 dark:border-gray-800 rounded-2xl shadow-sm overflow-hidden 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.3s;">
|
|
<div class="flex items-center gap-2 px-5 py-4 border-b border-gray-100 dark:border-gray-800">
|
|
<x-ri-information-line class="size-5 text-primary" />
|
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Server Information</h2>
|
|
</div>
|
|
|
|
<div class="p-5">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
|
|
|
<!-- Specifications -->
|
|
<div class="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-4 border border-gray-100 dark:border-gray-700/50">
|
|
<div class="flex items-center gap-2 mb-3">
|
|
<div class="w-7 h-7 rounded-lg bg-blue-100 dark:bg-blue-900/50 flex items-center justify-center">
|
|
<x-ri-cpu-line class="size-3.5 text-blue-600 dark:text-blue-400" />
|
|
</div>
|
|
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300">Specifications</h3>
|
|
</div>
|
|
<div class="space-y-2 text-sm">
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500 dark:text-gray-400">vCPU</span>
|
|
<span class="font-medium text-gray-900 dark:text-white">{{ $allocatedCores }} cores</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500 dark:text-gray-400">Memory</span>
|
|
<span class="font-medium text-gray-900 dark:text-white">{{ $allocatedMemoryMiB > 0 ? number_format($allocatedMemoryMiB / 1024, 1) . ' GiB' : \Illuminate\Support\Number::fileSize($memoryTotal) }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500 dark:text-gray-400">Storage</span>
|
|
<span class="font-medium text-gray-900 dark:text-white">{{ $allocatedDiskGiB > 0 ? $allocatedDiskGiB . ' GiB' : ($diskTotal > 0 ? number_format($diskTotal / 1073741824, 0) . ' GiB' : 'N/A') }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Network -->
|
|
<div class="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-4 border border-gray-100 dark:border-gray-700/50">
|
|
<div class="flex items-center gap-2 mb-3">
|
|
<div class="w-7 h-7 rounded-lg bg-emerald-100 dark:bg-emerald-900/50 flex items-center justify-center">
|
|
<x-ri-exchange-line class="size-3.5 text-emerald-600 dark:text-emerald-400" />
|
|
</div>
|
|
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300">Network Transfer</h3>
|
|
</div>
|
|
<div class="space-y-2 text-sm">
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500 dark:text-gray-400">Inbound</span>
|
|
<span class="font-medium text-gray-900 dark:text-white">{{ \Illuminate\Support\Number::fileSize($totalNetIn) }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500 dark:text-gray-400">Outbound</span>
|
|
<span class="font-medium text-gray-900 dark:text-white">{{ \Illuminate\Support\Number::fileSize($totalNetOut) }}</span>
|
|
</div>
|
|
<div class="flex justify-between pt-2 border-t border-gray-200 dark:border-gray-700">
|
|
<span class="text-gray-500 dark:text-gray-400">Total</span>
|
|
<span class="font-semibold text-gray-900 dark:text-white">{{ \Illuminate\Support\Number::fileSize($totalNetIn + $totalNetOut) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Billing -->
|
|
<div class="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-4 border border-gray-100 dark:border-gray-700/50">
|
|
<div class="flex items-center gap-2 mb-3">
|
|
<div class="w-7 h-7 rounded-lg bg-violet-100 dark:bg-violet-900/50 flex items-center justify-center">
|
|
<x-ri-calendar-line class="size-3.5 text-violet-600 dark:text-violet-400" />
|
|
</div>
|
|
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300">Billing</h3>
|
|
</div>
|
|
<div class="space-y-2 text-sm">
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500 dark:text-gray-400">Product</span>
|
|
<span class="font-medium text-gray-900 dark:text-white truncate max-w-[100px]">{{ $productName }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500 dark:text-gray-400">Status</span>
|
|
<span class="font-medium {{ $serviceStatus === 'active' ? 'text-success' : 'text-warning' }}">{{ ucfirst($serviceStatus) }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500 dark:text-gray-400">Renewal</span>
|
|
<span class="font-medium text-gray-900 dark:text-white">{{ $serviceExpires?->format('M d, Y') ?? 'N/A' }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Infrastructure -->
|
|
<div class="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-4 border border-gray-100 dark:border-gray-700/50">
|
|
<div class="flex items-center gap-2 mb-3">
|
|
<div class="w-7 h-7 rounded-lg bg-amber-100 dark:bg-amber-900/50 flex items-center justify-center">
|
|
<x-ri-building-line class="size-3.5 text-amber-600 dark:text-amber-400" />
|
|
</div>
|
|
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300">Infrastructure</h3>
|
|
</div>
|
|
<div class="space-y-2 text-sm">
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500 dark:text-gray-400">Datacenter</span>
|
|
<span class="font-medium text-gray-900 dark:text-white">{{ $locationName }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500 dark:text-gray-400">Node</span>
|
|
<span class="font-medium text-gray-900 dark:text-white">{{ $nodeName }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500 dark:text-gray-400">VM ID</span>
|
|
<span class="font-medium font-mono text-gray-900 dark:text-white">{{ $server->vm_id ?? 'N/A' }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Identifiers -->
|
|
<div class="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-4 border border-gray-100 dark:border-gray-700/50">
|
|
<div class="flex items-center gap-2 mb-3">
|
|
<div class="w-7 h-7 rounded-lg bg-cyan-100 dark:bg-cyan-900/50 flex items-center justify-center">
|
|
<x-ri-fingerprint-line class="size-3.5 text-cyan-600 dark:text-cyan-400" />
|
|
</div>
|
|
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300">Identifiers</h3>
|
|
</div>
|
|
<div class="space-y-2 text-sm">
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500 dark:text-gray-400">Server ID</span>
|
|
<span class="font-medium font-mono text-gray-900 dark:text-white">{{ $server->id }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500 dark:text-gray-400">Service ID</span>
|
|
<span class="font-medium font-mono text-gray-900 dark:text-white">{{ $server->service_id ?? 'N/A' }}</span>
|
|
</div>
|
|
<div class="flex justify-between">
|
|
<span class="text-gray-500 dark:text-gray-400">Created</span>
|
|
<span class="font-medium text-gray-900 dark:text-white">{{ $server->created_at?->format('M d, Y') ?? 'N/A' }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Live Usage -->
|
|
<div class="bg-gray-50 dark:bg-gray-800/50 rounded-xl p-4 border border-gray-100 dark:border-gray-700/50">
|
|
<div class="flex items-center gap-2 mb-3">
|
|
<div class="w-7 h-7 rounded-lg bg-rose-100 dark:bg-rose-900/50 flex items-center justify-center">
|
|
<x-ri-line-chart-line class="size-3.5 text-rose-600 dark:text-rose-400" />
|
|
</div>
|
|
<h3 class="text-sm font-semibold text-gray-700 dark:text-gray-300">Live Usage</h3>
|
|
</div>
|
|
<div class="space-y-2.5 text-sm">
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-gray-500 dark:text-gray-400">CPU</span>
|
|
<div class="flex items-center gap-2">
|
|
<div class="w-16 h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
<div class="h-full bg-blue-500 rounded-full" style="width: {{ min(100, $cpuUsagePercent) }}%"></div>
|
|
</div>
|
|
<span class="font-medium text-gray-900 dark:text-white text-xs w-9 text-right">{{ $cpuUsagePercent }}%</span>
|
|
</div>
|
|
</div>
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-gray-500 dark:text-gray-400">Memory</span>
|
|
<div class="flex items-center gap-2">
|
|
<div class="w-16 h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
<div class="h-full bg-emerald-500 rounded-full" style="width: {{ min(100, $memoryPercent) }}%"></div>
|
|
</div>
|
|
<span class="font-medium text-gray-900 dark:text-white text-xs w-9 text-right">{{ $memoryPercent }}%</span>
|
|
</div>
|
|
</div>
|
|
@if($diskTotal > 0)
|
|
<div class="flex justify-between items-center">
|
|
<span class="text-gray-500 dark:text-gray-400">Disk</span>
|
|
<div class="flex items-center gap-2">
|
|
<div class="w-16 h-1.5 bg-gray-200 dark:bg-gray-700 rounded-full overflow-hidden">
|
|
<div class="h-full bg-violet-500 rounded-full" style="width: {{ min(100, $diskPercent) }}%"></div>
|
|
</div>
|
|
<span class="font-medium text-gray-900 dark:text-white text-xs w-9 text-right">{{ $diskPercent }}%</span>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Windows Notice -->
|
|
@if($server->os && stripos($server->os->name, 'Windows') !== false)
|
|
<section class="bg-white dark:bg-gray-900 border border-warning/30 rounded-2xl shadow-sm overflow-hidden 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.35s;">
|
|
<div class="flex items-center gap-2 px-5 py-4 border-b border-warning/20 bg-warning/5">
|
|
<x-ri-windows-line class="size-5 text-warning" />
|
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">Windows Configuration</h2>
|
|
</div>
|
|
|
|
<div class="p-5">
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-4">
|
|
Windows doesn't support cloud-init. You'll need to manually configure networking and system settings.
|
|
</p>
|
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-3">
|
|
<div class="flex items-start gap-3 p-3 rounded-lg border border-info/20 bg-info/5">
|
|
<x-ri-settings-3-line class="size-4 text-info flex-shrink-0 mt-0.5" />
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-900 dark:text-white">Network</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">Configure IP manually</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-start gap-3 p-3 rounded-lg border border-warning/20 bg-warning/5">
|
|
<x-ri-key-line class="size-4 text-warning flex-shrink-0 mt-0.5" />
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-900 dark:text-white">Licensing</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">You provide license</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex items-start gap-3 p-3 rounded-lg border border-error/20 bg-error/5">
|
|
<x-ri-shield-check-line class="size-4 text-error flex-shrink-0 mt-0.5" />
|
|
<div>
|
|
<p class="text-xs font-semibold text-gray-900 dark:text-white">Compliance</p>
|
|
<p class="text-xs text-gray-500 dark:text-gray-400">Subject to audits</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
@endif
|
|
|
|
</div>
|
|
@endif
|
|
|
|
</x-proxmox::layout>
|