Deploy Ladill Hosting / deploy (push) Successful in 1m56s
Use the shared Inline sheet, hold busy until payment UI opens, and avoid locking page scroll during the handoff.
630 lines
48 KiB
PHP
630 lines
48 KiB
PHP
<x-user-layout>
|
|
<x-slot name="title">Hosting Account Details</x-slot>
|
|
|
|
@php
|
|
$statusColors = [
|
|
'active' => 'bg-emerald-50 text-emerald-700',
|
|
'suspended' => 'bg-red-50 text-red-700',
|
|
'pending' => 'bg-amber-50 text-amber-700',
|
|
'provisioning' => 'bg-blue-50 text-blue-700',
|
|
'failed' => 'bg-red-50 text-red-700',
|
|
];
|
|
|
|
$statusLabels = [
|
|
'active' => 'Active',
|
|
'suspended' => 'Suspended',
|
|
'pending' => 'Pending Setup',
|
|
'provisioning' => 'Setting Up...',
|
|
'failed' => 'Setup Failed',
|
|
];
|
|
|
|
$formErrors = $errors ?? new \Illuminate\Support\ViewErrorBag;
|
|
$addDomainAction = \Illuminate\Support\Facades\Route::has('hosting.panel.domains.add')
|
|
? route('hosting.panel.domains.add', $account)
|
|
: '#';
|
|
$panelIndexUrl = \Illuminate\Support\Facades\Route::has('hosting.panel.index')
|
|
? route('hosting.panel.index', $account)
|
|
: '#';
|
|
$panelSettingsUrl = \Illuminate\Support\Facades\Route::has('hosting.panel.settings')
|
|
? route('hosting.panel.settings', $account)
|
|
: '#';
|
|
$resourceStatusColors = [
|
|
'active' => 'bg-emerald-50 text-emerald-700',
|
|
'throttled' => 'bg-amber-50 text-amber-700',
|
|
'suspended' => 'bg-red-50 text-red-700',
|
|
];
|
|
@endphp
|
|
|
|
@php
|
|
$ownedDomainHosts = collect($ownedDomains)->map(fn ($d) => (string) $d)->all();
|
|
$oldDomain = trim((string) old('domain', ''));
|
|
$initialDomainSource = empty($ownedDomainHosts)
|
|
? 'external'
|
|
: ((string) old('is_owned_domain', '1') === '0' ? 'external' : 'ladill');
|
|
$selectedOwned = in_array($oldDomain, $ownedDomainHosts, true) ? $oldDomain : '';
|
|
$externalFallback = ! in_array($oldDomain, $ownedDomainHosts, true) ? $oldDomain : '';
|
|
@endphp
|
|
|
|
<script>if (window !== window.top) window.top.location.href = window.location.href;</script>
|
|
<div class="space-y-6" x-data="{
|
|
domainModal: {{ $formErrors->has('domain') || $formErrors->has('onboarding_mode') || $formErrors->has('document_root') ? 'true' : 'false' }},
|
|
panelModal: false,
|
|
renewModal: false,
|
|
selectedDuration: null,
|
|
showSheet: false,
|
|
checkoutUrl: '',
|
|
renewLoading: false,
|
|
async submitRenewal() {
|
|
this.renewLoading = true;
|
|
try {
|
|
const res = await fetch('{{ route('hosting.accounts.renew', $account) }}', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', 'X-CSRF-TOKEN': '{{ csrf_token() }}' },
|
|
body: JSON.stringify({ duration_months: this.selectedDuration }),
|
|
});
|
|
const data = await res.json();
|
|
if (!res.ok || data.error) { alert(data.error || 'Unable to start payment.'); this.renewLoading = false; return; }
|
|
this.renewModal = false;
|
|
this.checkoutUrl = data.checkout_url;
|
|
this.showSheet = true; // renewLoading cleared when payment UI opens (ladill-pay-opened)
|
|
} catch(e) {
|
|
alert('Network error. Please try again.');
|
|
this.renewLoading = false;
|
|
}
|
|
}
|
|
}">
|
|
{{-- Breadcrumb --}}
|
|
<nav class="flex items-center gap-1.5 text-sm text-slate-500">
|
|
<a href="{{ route('hosting.single-domain') }}" class="hover:text-slate-700 transition">Hosting</a>
|
|
<svg class="h-3.5 w-3.5 text-slate-400" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M8.25 4.5l7.5 7.5-7.5 7.5m0 0v-15"/></svg>
|
|
<span class="font-medium text-slate-800">{{ $accountLabel ?? ($account->primary_domain ?: $account->username) }}</span>
|
|
</nav>
|
|
|
|
{{-- Expired Account Banner --}}
|
|
@if ($account->isExpired() && $account->isInGracePeriod())
|
|
<div class="rounded-xl border border-red-200 bg-red-50 px-5 py-4">
|
|
<div class="flex items-start gap-3">
|
|
<svg class="h-5 w-5 shrink-0 text-red-500 mt-0.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"/></svg>
|
|
<div>
|
|
<h3 class="text-sm font-semibold text-red-900">Hosting Account Expired</h3>
|
|
<p class="text-sm text-red-700 mt-0.5">Your hosting expired on <span class="font-medium">{{ $account->expires_at->format('M d, Y') }}</span>. Your site is offline but your files are safe. Renew before <span class="font-medium">{{ $account->gracePeriodEndsAt()->format('M d, Y') }}</span> to avoid automatic deletion.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Pending Setup Banner --}}
|
|
@if ($account->status === 'pending')
|
|
<div class="rounded-xl border border-amber-200 bg-amber-50 px-5 py-4">
|
|
<div class="flex items-start gap-3">
|
|
<svg class="h-5 w-5 shrink-0 text-amber-500 mt-0.5 animate-spin" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992V4.356m-.984 13.287A9 9 0 1 1 19.07 7.93l1.945 1.418"/></svg>
|
|
<div>
|
|
<h3 class="text-sm font-semibold text-amber-900">Setting Up Your Hosting</h3>
|
|
<p class="text-sm text-amber-700 mt-0.5">Your hosting account is being set up on our servers. This usually takes 1-2 minutes. Please refresh this page shortly to check the status.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Provisioning Banner --}}
|
|
@if ($account->status === 'provisioning')
|
|
<div class="rounded-xl border border-blue-200 bg-blue-50 px-5 py-4">
|
|
<div class="flex items-start gap-3">
|
|
<svg class="h-5 w-5 shrink-0 text-blue-500 mt-0.5 animate-spin" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992V4.356m-.984 13.287A9 9 0 1 1 19.07 7.93l1.945 1.418"/></svg>
|
|
<div>
|
|
<h3 class="text-sm font-semibold text-blue-900">Provisioning In Progress</h3>
|
|
<p class="text-sm text-blue-700 mt-0.5">We're creating your hosting environment, setting up PHP, and configuring your server. This typically takes 1-3 minutes. Please refresh this page to check progress.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Failed Setup Banner --}}
|
|
@if ($account->status === 'failed')
|
|
<div class="rounded-xl border border-red-200 bg-red-50 px-5 py-4">
|
|
<div class="flex items-start gap-3">
|
|
<svg class="h-5 w-5 shrink-0 text-red-500 mt-0.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 9v3.75m9-.75a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 3.75h.008v.008H12v-.008Z"/></svg>
|
|
<div>
|
|
<h3 class="text-sm font-semibold text-red-900">Setup Failed</h3>
|
|
<p class="text-sm text-red-700 mt-0.5">There was a problem setting up your hosting account. Our team has been notified and will resolve this shortly. If you need immediate assistance, please <a href="{{ ladill_account_url('/support-tickets/create') }}" class="underline font-medium hover:text-red-800">open a support ticket</a>.</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Hero Card --}}
|
|
<div class="relative overflow-hidden rounded-2xl border border-slate-200 bg-white">
|
|
<div class="absolute inset-0 bg-gradient-to-br from-indigo-50/60 via-white to-slate-50/40"></div>
|
|
<div class="relative flex flex-wrap items-start justify-between gap-4 px-6 py-6 sm:px-8 sm:py-7">
|
|
<div class="flex items-start gap-4">
|
|
<div class="flex h-14 w-14 shrink-0 items-center justify-center rounded-2xl bg-indigo-100 text-indigo-600">
|
|
<svg class="h-7 w-7" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24">
|
|
<path stroke-linecap="round" stroke-linejoin="round" d="M3 9l9-6 9 6m-1.5 12V10.332A48.36 48.36 0 0 0 12 9.75c-2.551 0-5.056.2-7.5.582V21M3 21h18M12 6.75h.008v.008H12V6.75Z"/>
|
|
</svg>
|
|
</div>
|
|
<div>
|
|
<h2 class="text-xl font-bold tracking-tight text-slate-900">Hosting Account</h2>
|
|
<div class="mt-1 flex flex-wrap items-center gap-2.5">
|
|
<span class="text-sm text-slate-700">{{ $accountLabel ?? ($account->primary_domain ?: $account->username) }}</span>
|
|
<span class="rounded-full px-2.5 py-0.5 text-[11px] font-semibold {{ $statusColors[$account->status ?? 'pending'] }}">
|
|
{{ $statusLabels[$account->status ?? 'Pending'] }}
|
|
</span>
|
|
<span class="rounded-full px-2.5 py-0.5 text-[11px] font-semibold {{ $resourceStatusColors[$account->resource_status ?? 'active'] }}">
|
|
{{ ucfirst($account->resource_status ?? 'active') }} resources
|
|
</span>
|
|
</div>
|
|
<p class="mt-1.5 text-xs text-slate-500">Created {{ $account->created_at->format('M d, Y') }}
|
|
@if ($account->expires_at) · Expires {{ $account->expires_at->format('M d, Y') }} @endif
|
|
</p>
|
|
</div>
|
|
</div>
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
@if ($account->assignedDurationMonths() && !empty($renewalOptions) && ($account->isExpired() || $account->expires_at?->isBefore(now()->addDays(30))))
|
|
<button @click="renewModal = true; selectedDuration = {{ collect($renewalOptions)->firstWhere('current', true)['months'] ?? $account->assignedDurationMonths() }}" type="button"
|
|
class="inline-flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition">
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M16.023 9.348h4.992V4.356m-.984 13.287A9 9 0 1 1 19.07 7.93l1.945 1.418"/></svg>
|
|
Renew Plan
|
|
</button>
|
|
@endif
|
|
<button @click="panelModal = true" type="button"
|
|
class="btn-primary">
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.325.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.431l-1.003.827c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.955.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.47 6.47 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.281c-.09.543-.56.94-1.11.94h-2.594c-.55 0-1.019-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.431l1.004-.827c.292-.24.437-.613.43-.991a6.932 6.932 0 0 1 0-.255c.007-.38-.138-.751-.43-.992l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.086.22-.128.332-.183.582-.495.644-.869l.214-1.28Z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z"/></svg>
|
|
Control Panel
|
|
</button>
|
|
@if ($canLinkMoreDomains)
|
|
<button @click="domainModal = true" type="button"
|
|
class="inline-flex items-center gap-2 rounded-xl border border-slate-200 bg-white px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition">
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15"/></svg>
|
|
Link Domain
|
|
</button>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
@if (!empty($upsellRecommendations))
|
|
<div class="rounded-2xl border border-amber-200 bg-gradient-to-r from-amber-50 via-white to-white">
|
|
<div class="flex items-center justify-between gap-3 border-b border-amber-100 px-5 py-4">
|
|
<div>
|
|
<h3 class="text-sm font-semibold text-slate-900">Recommended Next Steps</h3>
|
|
<p class="mt-0.5 text-xs text-slate-500">These suggestions are based on how this account is being used right now.</p>
|
|
</div>
|
|
<span class="rounded-full bg-amber-100 px-2.5 py-1 text-[11px] font-semibold text-amber-700">{{ count($upsellRecommendations) }} suggestion{{ count($upsellRecommendations) === 1 ? '' : 's' }}</span>
|
|
</div>
|
|
<div class="grid gap-4 px-5 py-4 lg:grid-cols-2">
|
|
@foreach ($upsellRecommendations as $recommendation)
|
|
<div class="rounded-xl border border-amber-100 bg-white p-4">
|
|
<div class="flex items-start justify-between gap-3">
|
|
<div>
|
|
<p class="text-sm font-semibold text-slate-900">{{ $recommendation['title'] }}</p>
|
|
<p class="mt-1 text-xs leading-5 text-slate-500">{{ $recommendation['message'] }}</p>
|
|
</div>
|
|
<span class="rounded-full bg-slate-100 px-2.5 py-1 text-[11px] font-semibold text-slate-700">
|
|
{{ $recommendation['type'] === 'plan_upgrade' ? 'Upgrade' : 'Add-on' }}
|
|
</span>
|
|
</div>
|
|
|
|
@if (($recommendation['type'] ?? null) === 'plan_upgrade' && !empty($recommendation['target_product_id']))
|
|
<form method="POST" action="{{ route('hosting.accounts.plan-change', $account) }}" class="mt-4">
|
|
@csrf
|
|
<input type="hidden" name="target_product_id" value="{{ $recommendation['target_product_id'] }}">
|
|
<button type="submit" class="inline-flex items-center rounded-lg bg-slate-900 px-3.5 py-2 text-xs font-semibold text-white transition hover:bg-slate-800">
|
|
{{ $recommendation['cta_label'] }}
|
|
</button>
|
|
</form>
|
|
@elseif (!empty($recommendation['cta_url']))
|
|
<div class="mt-4">
|
|
<a href="{{ $recommendation['cta_url'] }}" class="inline-flex items-center rounded-lg bg-slate-900 px-3.5 py-2 text-xs font-semibold text-white transition hover:bg-slate-800">
|
|
{{ $recommendation['cta_label'] }}
|
|
</a>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endif
|
|
|
|
{{-- Info Cards Grid --}}
|
|
<div class="grid grid-cols-1 gap-5 lg:grid-cols-4">
|
|
{{-- Account Details Card --}}
|
|
<div class="rounded-2xl border border-slate-200 bg-white">
|
|
<div class="flex items-center gap-2.5 border-b border-slate-100 px-5 py-4">
|
|
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-blue-50 text-blue-600">
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M3 9l9-6 9 6m-1.5 12V10.332A48.36 48.36 0 0 0 12 9.75c-2.551 0-5.056.2-7.5.582V21M3 21h18M12 6.75h.008v.008H12V6.75Z"/></svg>
|
|
</div>
|
|
<h3 class="text-sm font-semibold text-slate-900">Account Details</h3>
|
|
</div>
|
|
<div class="px-5 py-4 space-y-3">
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-xs text-slate-500">Username</span>
|
|
<span class="text-sm font-medium text-slate-800">{{ $account->username }}</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-xs text-slate-500">Node IP</span>
|
|
<span class="text-sm font-medium text-slate-800">{{ $account->node?->ip_address ?? '161.97.138.149' }}</span>
|
|
</div>
|
|
@if ($account->product)
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-xs text-slate-500">Product</span>
|
|
<span class="text-sm font-medium text-slate-800">{{ $account->product->name }}</span>
|
|
</div>
|
|
@endif
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-xs text-slate-500">Email Usage</span>
|
|
<span class="text-sm font-medium text-slate-800">
|
|
{{ $emailUsage['mailbox_count'] }}
|
|
@if ($emailUsage['free_allowance'] === null)
|
|
/ Unlimited included
|
|
@else
|
|
/ {{ $emailUsage['free_allowance'] }} included
|
|
@endif
|
|
</span>
|
|
</div>
|
|
@if ($emailUsage['extra_count'] > 0)
|
|
<div class="rounded-lg bg-amber-50 px-3 py-2 text-xs text-amber-700">
|
|
Extra Emails: {{ $emailUsage['extra_count'] }} x GHS 5 = GHS {{ number_format($emailUsage['extra_total'], 2) }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Resources Card --}}
|
|
<div class="rounded-2xl border border-slate-200 bg-white">
|
|
<div class="flex items-center gap-2.5 border-b border-slate-100 px-5 py-4">
|
|
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-violet-50 text-violet-600">
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Z"/></svg>
|
|
</div>
|
|
<h3 class="text-sm font-semibold text-slate-900">Resources</h3>
|
|
</div>
|
|
<div class="px-5 py-4 space-y-3">
|
|
@php
|
|
$diskLimit = max(($account->allocated_disk_gb ?: ($account->product?->disk_gb ?? 10)), 1) * 1073741824;
|
|
$bandwidthLimit = $account->resource_limits['bandwidth_limit_bytes'] ?? 107374182400; // 100GB default
|
|
$diskUsed = $account->disk_used_bytes ?? 0;
|
|
$bandwidthUsed = $account->bandwidth_used_bytes ?? 0;
|
|
$diskPercent = $diskLimit > 0 ? ($diskUsed / $diskLimit) * 100 : 0;
|
|
$bandwidthPercent = $bandwidthLimit > 0 ? ($bandwidthUsed / $bandwidthLimit) * 100 : 0;
|
|
@endphp
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<span class="text-xs text-slate-500">Disk Space</span>
|
|
<span class="text-xs text-slate-500">{{ number_format($diskUsed / 1073741824, 1) }} GB / {{ number_format($diskLimit / 1073741824, 1) }} GB</span>
|
|
</div>
|
|
<div class="w-full rounded-full bg-slate-200 h-1.5">
|
|
<div class="rounded-full bg-gradient-to-r from-blue-500 to-blue-600 h-1.5" style="width: {{ min($diskPercent, 100) }}%"></div>
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="flex items-center justify-between mb-1">
|
|
<span class="text-xs text-slate-500">Bandwidth</span>
|
|
<span class="text-xs text-slate-500">{{ number_format($bandwidthUsed / 1073741824, 1) }} GB / {{ number_format($bandwidthLimit / 1073741824, 1) }} GB</span>
|
|
</div>
|
|
<div class="w-full rounded-full bg-slate-200 h-1.5">
|
|
<div class="rounded-full bg-gradient-to-r from-purple-500 to-purple-600 h-1.5" style="width: {{ min($bandwidthPercent, 100) }}%"></div>
|
|
</div>
|
|
</div>
|
|
@if ($account->php_version)
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-xs text-slate-500">PHP Version</span>
|
|
<span class="text-xs font-medium text-slate-800">{{ $account->php_version }}</span>
|
|
</div>
|
|
@endif
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-xs text-slate-500">CPU</span>
|
|
<span class="text-xs font-medium text-slate-800">{{ number_format((float) ($account->cpu_usage_percent ?? 0), 1) }}% / {{ $account->cpu_limit_percent ?? '-' }}%</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-xs text-slate-500">Memory</span>
|
|
<span class="text-xs font-medium text-slate-800">{{ $account->memory_used_mb ?? 0 }} MB / {{ $account->memory_limit_mb ?? '-' }} MB</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-xs text-slate-500">Processes</span>
|
|
<span class="text-xs font-medium text-slate-800">{{ $account->process_count ?? 0 }} / {{ $account->process_limit ?? '-' }}</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-xs text-slate-500">Inodes</span>
|
|
<span class="text-xs font-medium text-slate-800">{{ number_format((int) ($account->inode_count ?? 0)) }} / {{ number_format((int) ($account->inode_limit ?? 0)) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Features Card --}}
|
|
<div class="rounded-2xl border border-slate-200 bg-white">
|
|
<div class="flex items-center gap-2.5 border-b border-slate-100 px-5 py-4">
|
|
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-50 text-emerald-600">
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M11.48 3.499a.562.562 0 0 1 1.04 0l2.125 5.111a.563.563 0 0 0 .582.416l5.135.1a.564.564 0 0 1 .314.988l-3.74 3.247a.563.563 0 0 0-.182.613l1.28 3.982a.562.562 0 0 1-.84.61l-4.652-2.52a.562.562 0 0 0-.6 0L6.68 19.03a.562.562 0 0 1-.84-.61l1.28-3.982a.562.562 0 0 0-.182-.613l-3.74-3.247a.562.562 0 0 1 .314-.988l5.135-.1a.562.562 0 0 0 .582-.416l2.125-5.11Z"/></svg>
|
|
</div>
|
|
<h3 class="text-sm font-semibold text-slate-900">Features</h3>
|
|
</div>
|
|
<div class="px-5 py-4 space-y-2">
|
|
@php
|
|
$features = $account->features_enabled ?? [];
|
|
$defaultFeatures = ['SFTP Access', 'FTP Access', 'Email Accounts', 'MySQL Database'];
|
|
$displayFeatures = !empty($features) ? $features : $defaultFeatures;
|
|
@endphp
|
|
@foreach ($displayFeatures as $feature)
|
|
<div class="flex items-center gap-2">
|
|
<div class="h-1.5 w-1.5 rounded-full bg-emerald-500"></div>
|
|
<span class="text-xs text-slate-700">{{ $feature }}</span>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Health Card --}}
|
|
<div class="rounded-2xl border border-slate-200 bg-white">
|
|
<div class="flex items-center gap-2.5 border-b border-slate-100 px-5 py-4">
|
|
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-emerald-50 text-emerald-600">
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z"/></svg>
|
|
</div>
|
|
<h3 class="text-sm font-semibold text-slate-900">Health</h3>
|
|
</div>
|
|
<div class="px-5 py-4 space-y-3">
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-xs text-slate-500">Status</span>
|
|
<span class="text-xs font-semibold {{ $account->status === 'active' ? 'text-emerald-600' : ($account->status === 'suspended' ? 'text-red-600' : 'text-amber-600') }}">
|
|
{{ ucfirst($account->status ?? 'Unknown') }}
|
|
</span>
|
|
</div>
|
|
@if ($account->provisioned_at)
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-xs text-slate-500">Provisioned</span>
|
|
<span class="text-xs text-slate-600">{{ $account->provisioned_at->format('M d, Y') }}</span>
|
|
</div>
|
|
@endif
|
|
@if ($account->suspended_at)
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-xs text-slate-500">Suspended</span>
|
|
<span class="text-xs text-slate-600">{{ $account->suspended_at->format('M d, Y') }}</span>
|
|
</div>
|
|
@endif
|
|
@if (!empty($account->suspension_reason))
|
|
<div class="text-xs text-red-600 bg-red-50 rounded px-2 py-1">
|
|
{{ $account->suspension_reason }}
|
|
</div>
|
|
@endif
|
|
@if ($account->uploads_restricted)
|
|
<div class="text-xs text-amber-700 bg-amber-50 rounded px-2 py-1">
|
|
Uploads are temporarily restricted because the inode limit has been reached.
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Domain Card --}}
|
|
<div class="rounded-2xl border border-slate-200 bg-white">
|
|
<div class="flex items-center justify-between border-b border-slate-100 px-5 py-4">
|
|
<div class="flex items-center gap-2.5">
|
|
<div class="flex h-8 w-8 items-center justify-center rounded-lg bg-blue-50 text-blue-600">
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244"/></svg>
|
|
</div>
|
|
<h3 class="text-sm font-semibold text-slate-900">Domains</h3>
|
|
</div>
|
|
@if ($canLinkMoreDomains)
|
|
<button @click="domainModal = true" type="button" class="text-xs font-semibold text-indigo-600 hover:text-indigo-800 transition">+ Add</button>
|
|
@endif
|
|
</div>
|
|
<div class="px-5 py-4">
|
|
@if ($linkedSites->isNotEmpty())
|
|
<div class="space-y-3">
|
|
@foreach ($linkedSites as $site)
|
|
<div class="flex items-start justify-between gap-3 rounded-xl border border-slate-100 bg-slate-50/70 px-4 py-3">
|
|
<div>
|
|
<div class="flex flex-wrap items-center gap-2">
|
|
<span class="text-sm font-medium text-slate-800">{{ $site->domain }}</span>
|
|
@if ($site->type === 'primary')
|
|
<span class="rounded-full bg-blue-100 px-2 py-0.5 text-[11px] font-semibold text-blue-700">Primary</span>
|
|
@endif
|
|
@if ($site->ssl_enabled)
|
|
<span class="rounded-full bg-emerald-100 px-2 py-0.5 text-[11px] font-semibold text-emerald-700">SSL</span>
|
|
@endif
|
|
</div>
|
|
<p class="mt-1 text-xs text-slate-500">{{ $site->document_root }}</p>
|
|
</div>
|
|
<span class="rounded-full px-2 py-0.5 text-[11px] font-semibold {{ $site->status === 'active' ? 'bg-emerald-100 text-emerald-700' : 'bg-amber-100 text-amber-700' }}">
|
|
{{ ucfirst($site->status) }}
|
|
</span>
|
|
</div>
|
|
@endforeach
|
|
</div>
|
|
<p class="mt-3 text-xs text-slate-400">
|
|
@if ($maxDomains === -1)
|
|
{{ $account->domainSitesCount() }} domain {{ $account->domainSitesCount() === 1 ? 'slot' : 'slots' }} in use (unlimited).
|
|
@else
|
|
{{ $account->domainSitesCount() }} of {{ $maxDomains }} domain {{ $maxDomains === 1 ? 'slot' : 'slots' }} in use.
|
|
@endif
|
|
· {{ $account->subdomainSitesCount() }}{{ $account->maxSubdomainsLimit() === -1 ? '' : ' of '.$account->maxSubdomainsLimit() }} subdomain {{ $account->subdomainSitesCount() === 1 ? 'slot' : 'slots' }} in use.
|
|
</p>
|
|
@elseif ($account->primary_domain && ! \App\Services\Hosting\PlaceholderPrimaryDomainService::isPlaceholderDomain($account->primary_domain))
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-sm font-medium text-slate-800">{{ $account->primary_domain }}</span>
|
|
<span class="rounded-full px-2 py-0.5 text-[11px] font-semibold bg-emerald-100 text-emerald-700">
|
|
Active
|
|
</span>
|
|
</div>
|
|
<p class="mt-2 text-xs text-slate-400">Primary domain for this hosting account.</p>
|
|
@else
|
|
<div class="flex flex-col items-center py-4 text-center">
|
|
<p class="text-sm text-slate-500">No domain linked</p>
|
|
<p class="mt-0.5 text-xs text-slate-400">Link a domain to use your hosting account.</p>
|
|
@if ($canLinkMoreDomains)
|
|
<button @click="domainModal = true" type="button" class="mt-2.5 text-xs font-semibold text-indigo-600 hover:text-indigo-800 transition">Connect a domain</button>
|
|
@endif
|
|
</div>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
|
|
{{-- Domain Linking Modal --}}
|
|
<template x-teleport="body">
|
|
<div x-show="domainModal" x-cloak class="fixed inset-0 z-50 overflow-y-auto sm:flex sm:items-center sm:justify-center sm:p-4" @keydown.escape.window="domainModal = false">
|
|
<div x-show="domainModal" 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"
|
|
class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm hidden sm:block" @click="domainModal = false"></div>
|
|
|
|
<div x-show="domainModal" 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"
|
|
class="relative min-h-full sm:min-h-0 w-full sm:max-w-lg sm:rounded-2xl sm:border sm:border-slate-200 bg-white shadow-xl" @click.stop>
|
|
|
|
<form method="POST" action="{{ $addDomainAction }}"
|
|
x-data="ladillDomainSourceForm({
|
|
domainSource: @js($initialDomainSource),
|
|
selectedOwnedDomain: @js($selectedOwned),
|
|
externalDomain: @js($externalFallback),
|
|
ownedDomains: @js($ownedDomainHosts),
|
|
ownedUrl: @js(route('hosting.domains.owned', ['exclude' => $linkedSites->pluck('domain')->filter()->all()])),
|
|
onboardingMode: @js(old('onboarding_mode', 'ns_auto')),
|
|
})">
|
|
@csrf
|
|
|
|
<div class="flex items-center justify-between border-b border-slate-100 px-6 py-4">
|
|
<h3 class="text-base font-semibold text-slate-900">Connect a Domain</h3>
|
|
<button @click="domainModal = false" type="button" class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition">
|
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/></svg>
|
|
</button>
|
|
</div>
|
|
<div class="p-6 space-y-5">
|
|
<x-domain-source-fields
|
|
:owned-domains="$ownedDomainHosts"
|
|
owned-url="{{ route('hosting.domains.owned') }}"
|
|
show-connection-method
|
|
/>
|
|
|
|
<div class="flex items-center justify-end gap-3 pt-1">
|
|
<button @click="domainModal = false" type="button" class="rounded-xl border border-slate-200 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition">Cancel</button>
|
|
<button type="submit" class="btn-primary">Add Domain</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
{{-- Renewal Duration Modal --}}
|
|
@if (!empty($renewalOptions))
|
|
<template x-teleport="body">
|
|
<div x-show="renewModal" x-cloak class="fixed inset-0 z-50 overflow-y-auto sm:flex sm:items-center sm:justify-center sm:p-4" @keydown.escape.window="renewModal = false">
|
|
<div x-show="renewModal" 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"
|
|
class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm hidden sm:block" @click="renewModal = false"></div>
|
|
|
|
<div x-show="renewModal" 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"
|
|
class="relative min-h-full sm:min-h-0 w-full sm:max-w-md sm:rounded-2xl sm:border sm:border-slate-200 bg-white shadow-xl" @click.stop>
|
|
|
|
<div>
|
|
|
|
<div class="flex items-center justify-between border-b border-slate-100 px-6 py-4">
|
|
<div>
|
|
<h3 class="text-base font-semibold text-slate-900">Renew Hosting Plan</h3>
|
|
<p class="mt-0.5 text-xs text-slate-500">{{ $accountLabel ?? ($account->primary_domain ?: $account->username) }}</p>
|
|
</div>
|
|
<button @click="renewModal = false" type="button" class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition">
|
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/></svg>
|
|
</button>
|
|
</div>
|
|
|
|
<div class="p-6 space-y-4">
|
|
<p class="text-sm text-slate-600">Choose a renewal duration:</p>
|
|
|
|
<div class="space-y-2">
|
|
@foreach ($renewalOptions as $option)
|
|
<label class="flex cursor-pointer items-center justify-between gap-3 rounded-xl border p-4 transition"
|
|
:class="selectedDuration === {{ $option['months'] }} ? 'border-indigo-300 bg-indigo-50/50 ring-1 ring-indigo-200' : 'border-slate-200 hover:border-slate-300'">
|
|
<div class="flex items-center gap-3">
|
|
<input type="radio" name="duration_radio" value="{{ $option['months'] }}"
|
|
x-model.number="selectedDuration"
|
|
class="text-indigo-600 focus:ring-indigo-500">
|
|
<div>
|
|
<p class="text-sm font-medium text-slate-900">{{ $option['label'] }}</p>
|
|
@if ($option['current'])
|
|
<p class="text-[11px] text-indigo-600 font-medium">Current plan term</p>
|
|
@endif
|
|
</div>
|
|
</div>
|
|
<span class="text-sm font-semibold text-slate-800">{{ $option['currency'] }} {{ number_format($option['amount'], 2) }}</span>
|
|
</label>
|
|
@endforeach
|
|
</div>
|
|
|
|
@if ($account->expires_at)
|
|
<div class="rounded-lg bg-slate-50 px-3 py-2 text-xs text-slate-500">
|
|
Current expiry: <span class="font-medium text-slate-700">{{ $account->expires_at->format('M d, Y') }}</span>
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="flex items-center justify-end gap-3 border-t border-slate-100 px-6 py-4">
|
|
<button @click="renewModal = false" type="button" class="rounded-xl border border-slate-200 px-4 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition">Cancel</button>
|
|
<button type="button" @click="submitRenewal()" :disabled="!selectedDuration || renewLoading"
|
|
class="btn-primary disabled:opacity-50 disabled:cursor-not-allowed">
|
|
<svg x-show="renewLoading" x-cloak class="h-4 w-4 animate-spin" fill="none" viewBox="0 0 24 24"><circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/><path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"/></svg>
|
|
<svg x-show="!renewLoading" class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M2.25 8.25h19.5M2.25 9h19.5m-16.5 5.25h6m-6 2.25h3m-3.75 3h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Z"/></svg>
|
|
<span x-text="renewLoading ? 'Processing…' : 'Proceed to Payment'">Proceed to Payment</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
@endif
|
|
|
|
@include('partials.paystack-sheet')
|
|
|
|
{{-- Control Panel Modal --}}
|
|
<template x-teleport="body">
|
|
<div x-show="panelModal" x-cloak class="fixed inset-0 z-50 overflow-y-auto sm:flex sm:items-center sm:justify-center sm:p-4" @keydown.escape.window="panelModal = false">
|
|
<div x-show="panelModal" 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"
|
|
class="fixed inset-0 bg-slate-900/50 backdrop-blur-sm hidden sm:block" @click="panelModal = false"></div>
|
|
|
|
<div x-show="panelModal" 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"
|
|
class="relative min-h-full sm:min-h-0 w-full sm:max-w-lg sm:rounded-2xl sm:border sm:border-slate-200 bg-white shadow-xl" @click.stop>
|
|
<div class="flex items-center justify-between border-b border-slate-100 px-6 py-4">
|
|
<h3 class="text-base font-semibold text-slate-900">Control Panel Access</h3>
|
|
<button @click="panelModal = false" type="button" class="rounded-lg p-1 text-slate-400 hover:bg-slate-100 hover:text-slate-600 transition">
|
|
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M6 18 18 6M6 6l12 12"/></svg>
|
|
</button>
|
|
</div>
|
|
<div class="p-6 space-y-5">
|
|
<div class="rounded-xl border border-slate-200 bg-slate-50 p-4">
|
|
<p class="text-sm font-medium text-slate-900 mb-4">SFTP Access Details:</p>
|
|
<div class="space-y-4">
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-sm font-medium text-slate-700">Host:</span>
|
|
<span class="text-sm font-mono text-slate-600">{{ $account->node?->ip_address ?? '161.97.138.149' }}</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-sm font-medium text-slate-700">Port:</span>
|
|
<span class="text-sm font-mono text-slate-600">22</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-sm font-medium text-slate-700">Username:</span>
|
|
<span class="text-sm font-mono text-slate-600">{{ $account->username }}</span>
|
|
</div>
|
|
<div class="flex items-center justify-between">
|
|
<span class="text-sm font-medium text-slate-700">Password:</span>
|
|
<span class="text-sm text-slate-500">Set in Control Panel Settings</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="grid gap-3">
|
|
<a href="{{ $panelIndexUrl }}" class="btn-primary">
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 011.37.49l1.296 2.247a1.125 1.125 0 01-.26 1.431l-1.003.827c-.293.24-.438.613-.431.992a6.759 6.759 0 010 .255c-.007.378.138.75.43.99l1.005.828c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 01-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 01-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 01-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 01-1.369-.49l-1.297-2.247a1.125 1.125 0 01.26-1.431l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 010-.255c.007-.378-.138-.75-.43-.99l-1.004-.828a1.125 1.125 0 01-.26-1.43l1.297-2.247a1.125 1.125 0 011.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.644-.869l.214-1.281z"/><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"/></svg>
|
|
Open Control Panel
|
|
</a>
|
|
<a href="{{ $panelSettingsUrl }}" class="inline-flex items-center justify-center gap-2 rounded-xl border border-slate-200 bg-white px-5 py-2.5 text-sm font-medium text-slate-700 hover:bg-slate-50 transition">
|
|
<svg class="h-4 w-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 013 3m3 0a6 6 0 01-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499c.404-.404.527-1 .43-1.563A6 6 0 1121.75 8.25z"/></svg>
|
|
Change SFTP Password
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</x-user-layout>
|