Deploy Ladill Hosting / deploy (push) Successful in 28s
Add shared btn-primary components and plus icons on create/new actions for a consistent Ladill UI. Co-authored-by: Cursor <cursoragent@cursor.com>
264 lines
19 KiB
PHP
264 lines
19 KiB
PHP
<x-hosting-panel-layout :account="$account">
|
||
<x-slot name="title">Domains - {{ $account->username }}</x-slot>
|
||
<x-slot name="header">Domains</x-slot>
|
||
|
||
<div class="space-y-6">
|
||
@if(session('success'))
|
||
<div class="rounded-lg bg-emerald-50 border border-emerald-200 p-4">
|
||
<p class="text-sm text-emerald-800">{{ session('success') }}</p>
|
||
</div>
|
||
@endif
|
||
|
||
@if(session('error'))
|
||
<div class="rounded-lg bg-red-50 border border-red-200 p-4">
|
||
<p class="text-sm text-red-800">{{ session('error') }}</p>
|
||
</div>
|
||
@endif
|
||
|
||
@php
|
||
$ownedDomainHosts = $ownedDomains->map(fn ($d) => (string) $d)->all();
|
||
$oldDomain = trim((string) old('domain', ''));
|
||
$initialDomainSource = $ownedDomains->isEmpty()
|
||
? 'external'
|
||
: ((string) old('is_owned_domain', '1') === '0' ? 'external' : 'ladill');
|
||
$selectedOwnedDomain = in_array($oldDomain, $ownedDomainHosts, true) ? $oldDomain : '';
|
||
$externalFallbackDomain = ! in_array($oldDomain, $ownedDomainHosts, true) ? $oldDomain : '';
|
||
@endphp
|
||
|
||
<div class="rounded-xl border border-slate-200 bg-white p-6">
|
||
<h3 class="text-base font-semibold text-slate-900 mb-4">Add Domain</h3>
|
||
<form action="{{ route('hosting.panel.domains.add', $account) }}" method="POST" class="space-y-5"
|
||
x-data="{
|
||
domainSource: @js($initialDomainSource),
|
||
selectedOwnedDomain: @js($selectedOwnedDomain),
|
||
externalDomain: @js($externalFallbackDomain),
|
||
onboardingMode: '{{ old('onboarding_mode', 'ns_auto') }}'
|
||
}">
|
||
@csrf
|
||
<input type="hidden" name="is_owned_domain" :value="domainSource === 'ladill' ? '1' : '0'">
|
||
|
||
{{-- Domain Source Toggle --}}
|
||
<div>
|
||
<div class="mb-2 flex items-center justify-between">
|
||
<label class="text-sm font-medium text-slate-700">Domain</label>
|
||
<div class="inline-flex items-center rounded-md bg-slate-100 p-0.5 text-[11px] font-medium">
|
||
<button type="button" @click="domainSource = 'ladill'"
|
||
:class="domainSource === 'ladill' ? 'bg-white text-slate-900 shadow-sm' : 'text-slate-500'"
|
||
class="rounded px-2 py-1 transition">Ladill</button>
|
||
<button type="button" @click="domainSource = 'external'"
|
||
:class="domainSource === 'external' ? 'bg-white text-slate-900 shadow-sm' : 'text-slate-500'"
|
||
class="rounded px-2 py-1 transition">External</button>
|
||
</div>
|
||
</div>
|
||
|
||
{{-- Ladill (Owned) Domains --}}
|
||
<div x-show="domainSource === 'ladill'" x-cloak>
|
||
@if($ownedDomains->isNotEmpty())
|
||
<select name="domain" x-model="selectedOwnedDomain" :disabled="domainSource !== 'ladill'"
|
||
class="block w-full rounded-lg border-slate-200 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||
<option value="">Choose a domain...</option>
|
||
@foreach($ownedDomains as $domain)
|
||
<option value="{{ $domain }}">{{ $domain }}</option>
|
||
@endforeach
|
||
</select>
|
||
<p class="mt-1.5 text-xs text-slate-500">
|
||
Don't have a domain?
|
||
<a href="{{ ladill_domains_url('/find') }}" class="font-medium text-indigo-600 hover:text-indigo-800">Purchase one first</a>
|
||
</p>
|
||
@else
|
||
<div class="rounded-lg bg-slate-50 px-4 py-3 text-center text-sm text-slate-600">
|
||
No domains found in your account.
|
||
<a href="{{ ladill_domains_url('/find') }}" class="font-medium text-indigo-600 underline underline-offset-2 hover:text-indigo-800">Register a domain</a>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- External Domain --}}
|
||
<div x-show="domainSource === 'external'" x-cloak>
|
||
<input type="text" name="domain" x-model="externalDomain" :disabled="domainSource !== 'external'" required placeholder="example.com"
|
||
class="block w-full rounded-lg border-slate-200 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500 @error('domain') border-red-300 @enderror">
|
||
<p class="mt-1.5 text-xs text-slate-500">Enter your domain without www or http (e.g., example.com)</p>
|
||
</div>
|
||
|
||
@error('domain')
|
||
<p class="mt-1.5 text-sm text-red-600">{{ $message }}</p>
|
||
@enderror
|
||
</div>
|
||
|
||
{{-- Onboarding Mode (External only) --}}
|
||
<div x-show="domainSource === 'external'" x-cloak>
|
||
<label class="block text-sm font-medium text-slate-700 mb-3">Connection Method</label>
|
||
<div class="space-y-3">
|
||
<label class="relative flex cursor-pointer rounded-lg border border-slate-200 bg-white p-4 hover:bg-slate-50 transition">
|
||
<input type="radio" name="onboarding_mode" value="ns_auto" x-model="onboardingMode" class="sr-only peer">
|
||
<div class="flex flex-1 items-start gap-3">
|
||
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-emerald-100 text-emerald-600">
|
||
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M5.25 14.25h13.5m-13.5 0a3 3 0 0 1-3-3m3 3a3 3 0 1 0 0 6h13.5a3 3 0 1 0 0-6m-16.5-3a3 3 0 0 1 3-3h13.5a3 3 0 0 1 3 3m-19.5 0a4.5 4.5 0 0 1 .9-2.7L5.737 5.1a3.375 3.375 0 0 1 2.7-1.35h7.126c1.062 0 2.062.5 2.7 1.35l2.587 3.45a4.5 4.5 0 0 1 .9 2.7m0 0a3 3 0 0 1-3 3m0 3h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Zm-3 6h.008v.008h-.008v-.008Zm0-6h.008v.008h-.008v-.008Z"/></svg>
|
||
</div>
|
||
<div class="flex-1">
|
||
<p class="text-sm font-semibold text-slate-900">Point Nameservers</p>
|
||
<p class="mt-0.5 text-xs text-slate-500">Point your domain's nameservers to Ladill. We'll manage DNS and SSL automatically.</p>
|
||
</div>
|
||
</div>
|
||
<div class="absolute right-4 top-4 hidden h-5 w-5 items-center justify-center rounded-full bg-indigo-600 text-white peer-checked:flex">
|
||
<svg class="h-3 w-3" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
|
||
</div>
|
||
<div class="absolute inset-0 rounded-lg border-2 border-transparent peer-checked:border-indigo-600 pointer-events-none"></div>
|
||
</label>
|
||
|
||
<label class="relative flex cursor-pointer rounded-lg border border-slate-200 bg-white p-4 hover:bg-slate-50 transition">
|
||
<input type="radio" name="onboarding_mode" value="manual_dns" x-model="onboardingMode" class="sr-only peer">
|
||
<div class="flex flex-1 items-start gap-3">
|
||
<div class="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-blue-100 text-blue-600">
|
||
<svg class="h-5 w-5" fill="none" stroke="currentColor" stroke-width="1.5" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z"/></svg>
|
||
</div>
|
||
<div class="flex-1">
|
||
<p class="text-sm font-semibold text-slate-900">Add DNS Records</p>
|
||
<p class="mt-0.5 text-xs text-slate-500">Keep your current DNS provider and manually add A records pointing to our server.</p>
|
||
</div>
|
||
</div>
|
||
<div class="absolute right-4 top-4 hidden h-5 w-5 items-center justify-center rounded-full bg-indigo-600 text-white peer-checked:flex">
|
||
<svg class="h-3 w-3" fill="currentColor" viewBox="0 0 20 20"><path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd"/></svg>
|
||
</div>
|
||
<div class="absolute inset-0 rounded-lg border-2 border-transparent peer-checked:border-indigo-600 pointer-events-none"></div>
|
||
</label>
|
||
</div>
|
||
@error('onboarding_mode')
|
||
<p class="mt-1.5 text-sm text-red-600">{{ $message }}</p>
|
||
@enderror
|
||
</div>
|
||
|
||
{{-- Document Root (optional, both flows) --}}
|
||
<div>
|
||
<label class="block text-sm font-medium text-slate-700 mb-1">Document Root <span class="font-normal text-slate-400">(optional)</span></label>
|
||
<input type="text" name="document_root" placeholder="public_html/example.com" value="{{ old('document_root') }}"
|
||
class="w-full rounded-lg border-slate-200 text-sm shadow-sm focus:border-indigo-500 focus:ring-indigo-500">
|
||
<p class="mt-1 text-xs text-slate-500">Leave empty to use default: public_html/domain.com</p>
|
||
</div>
|
||
|
||
<button type="submit" 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="M12 4.5v15m7.5-7.5h-15"/></svg>
|
||
Add Domain
|
||
</button>
|
||
</form>
|
||
</div>
|
||
|
||
<div class="rounded-xl border border-slate-200 bg-white overflow-hidden">
|
||
<div class="px-6 py-4 border-b border-slate-200">
|
||
<h3 class="text-base font-semibold text-slate-900">Your Domains</h3>
|
||
</div>
|
||
@if($account->sites->count())
|
||
<table class="min-w-full divide-y divide-slate-200">
|
||
<thead class="bg-slate-50">
|
||
<tr>
|
||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">Domain</th>
|
||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">Document Root</th>
|
||
<th class="px-6 py-3 text-left text-xs font-medium text-slate-500 uppercase">Status</th>
|
||
<th class="px-6 py-3 text-right text-xs font-medium text-slate-500 uppercase">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody class="divide-y divide-slate-200">
|
||
@foreach($account->sites as $site)
|
||
<tr class="hover:bg-slate-50">
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
<div class="flex items-center gap-2">
|
||
<span class="text-sm font-medium text-slate-900">{{ $site->domain }}</span>
|
||
<a href="http://{{ $site->domain }}" target="_blank" class="text-slate-400 hover:text-indigo-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.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25"/></svg>
|
||
</a>
|
||
</div>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
<span class="text-sm font-mono text-slate-600">{{ $site->document_root }}</span>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap">
|
||
<span class="inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium {{ $site->status === 'active' ? 'bg-emerald-100 text-emerald-800' : 'bg-slate-100 text-slate-800' }}">
|
||
{{ ucfirst($site->status) }}
|
||
</span>
|
||
</td>
|
||
<td class="px-6 py-4 whitespace-nowrap text-right">
|
||
@if((int) $account->user_id === (int) auth()->id())
|
||
<x-confirm-dialog
|
||
:name="'remove-domain-'.$site->id"
|
||
title="Remove domain?"
|
||
message="Remove this domain? The files will not be deleted."
|
||
:action="route('hosting.panel.domains.remove', [$account, $site])"
|
||
method="DELETE"
|
||
confirm-label="Remove"
|
||
>
|
||
<x-slot:trigger>
|
||
<button type="button" class="inline-flex items-center gap-1.5 rounded-lg border border-red-200 bg-white px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50 transition">
|
||
<svg class="h-3.5 w-3.5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" d="M14.74 9l-.346 9m-4.788 0L9.26 9m9.968-3.21c.342.052.682.107 1.022.166m-1.022-.165L18.16 19.673a2.25 2.25 0 01-2.244 2.077H8.084a2.25 2.25 0 01-2.244-2.077L4.772 5.79m14.456 0a48.108 48.108 0 00-3.478-.397m-12 .562c.34-.059.68-.114 1.022-.165m0 0a48.11 48.11 0 013.478-.397m7.5 0v-.916c0-1.18-.91-2.164-2.09-2.201a51.964 51.964 0 00-3.32 0c-1.18.037-2.09 1.022-2.09 2.201v.916m7.5 0a48.667 48.667 0 00-7.5 0"/></svg>
|
||
Remove
|
||
</button>
|
||
</x-slot:trigger>
|
||
</x-confirm-dialog>
|
||
@else
|
||
<span class="text-xs text-slate-400">Owner only</span>
|
||
@endif
|
||
</td>
|
||
</tr>
|
||
@endforeach
|
||
</tbody>
|
||
</table>
|
||
@else
|
||
<div class="px-6 py-12 text-center">
|
||
@include('components.icons.domain-globe', ['class' => 'mx-auto h-12 w-12 text-slate-300'])
|
||
<p class="mt-4 text-sm text-slate-500">No addon domains yet. Add one above.</p>
|
||
</div>
|
||
@endif
|
||
</div>
|
||
|
||
@php
|
||
$serverIp = $account->node?->ip_address ?? '161.97.138.149';
|
||
@endphp
|
||
|
||
{{-- DNS Quick Reference --}}
|
||
<div class="rounded-xl border border-slate-200 bg-white p-6">
|
||
<h3 class="text-base font-semibold text-slate-900 mb-2">DNS Quick Reference</h3>
|
||
<p class="text-sm text-slate-600 mb-4">Use one of these methods to connect an external domain:</p>
|
||
|
||
<div class="grid gap-4 md:grid-cols-2">
|
||
<div class="rounded-lg border border-emerald-200 bg-emerald-50/50 p-4">
|
||
<div class="flex items-center gap-2 mb-2">
|
||
<h4 class="text-sm font-semibold text-slate-900">Nameservers</h4>
|
||
<span class="inline-flex items-center rounded-full bg-emerald-100 px-2 py-0.5 text-[10px] font-medium text-emerald-800">Recommended</span>
|
||
</div>
|
||
<div class="space-y-1.5">
|
||
<div class="flex items-center justify-between rounded bg-white border border-emerald-200 px-3 py-1.5">
|
||
<span class="text-sm font-mono text-slate-900">ns1.ladill.com</span>
|
||
<button type="button" onclick="navigator.clipboard.writeText('ns1.ladill.com')" class="text-xs text-indigo-600 hover:text-indigo-800">Copy</button>
|
||
</div>
|
||
<div class="flex items-center justify-between rounded bg-white border border-emerald-200 px-3 py-1.5">
|
||
<span class="text-sm font-mono text-slate-900">ns2.ladill.com</span>
|
||
<button type="button" onclick="navigator.clipboard.writeText('ns2.ladill.com')" class="text-xs text-indigo-600 hover:text-indigo-800">Copy</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="rounded-lg border border-slate-200 bg-slate-50/50 p-4">
|
||
<h4 class="text-sm font-semibold text-slate-900 mb-2">A Records</h4>
|
||
<div class="space-y-1 rounded bg-white border border-slate-200 p-3">
|
||
<div class="grid grid-cols-3 gap-2 text-xs">
|
||
<span class="font-medium text-slate-500">Type</span>
|
||
<span class="font-medium text-slate-500">Name</span>
|
||
<span class="font-medium text-slate-500">Value</span>
|
||
</div>
|
||
<div class="grid grid-cols-3 gap-2 text-xs">
|
||
<span class="font-mono text-slate-900">A</span>
|
||
<span class="font-mono text-slate-900">@</span>
|
||
<span class="font-mono text-slate-900">{{ $serverIp }}</span>
|
||
</div>
|
||
<div class="grid grid-cols-3 gap-2 text-xs">
|
||
<span class="font-mono text-slate-900">A</span>
|
||
<span class="font-mono text-slate-900">www</span>
|
||
<span class="font-mono text-slate-900">{{ $serverIp }}</span>
|
||
</div>
|
||
</div>
|
||
<p class="mt-2 text-xs text-slate-500">DNS changes can take up to 24–48 hours.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</x-hosting-panel-layout>
|