Files
Virtnet.bond/extensions/Servers/Proxmox/views/subdomain.blade.php
Joseph.Rawlings 0ed29d782e Refactor Proxmox views for improved styling and consistency
- Updated subdomain.blade.php to enhance button styles and modal transitions.
- Changed text colors for better readability and consistency across the UI.
- Refactored system.blade.php to unify button components and improve layout structure.
- Adjusted modal z-index values for consistent layering.
- Enhanced accessibility by ensuring proper text contrast and hover states.
2025-12-18 09:40:58 -05:00

462 lines
30 KiB
PHP

<x-proxmox::layout :server="$server" view="subdomain" :status="$status" :status-polling="true" :status-polling-interval="'2s'">
<div class="flex items-center justify-between mb-6">
<div>
<div class="flex items-center gap-2 min-w-0">
<h2 class="text-xl font-semibold break-words">Subdomain Management</h2>
<div class="relative" x-data="{ open: false }" x-cloak>
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors">
<x-ri-question-line class="size-4" />
</button>
<!-- Modal Backdrop -->
<div x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@click="open = false"
@keydown.escape.window="open = false"
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
style="display: none;">
</div>
<!-- Modal -->
<div x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
@click.away="open = false"
@keydown.escape.window="open = false"
class="fixed inset-0 z-50 flex items-center justify-center p-4"
style="display: none;">
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
<x-ri-close-line class="size-5" />
</button>
<div class="flex items-center gap-3 mb-4 pr-8">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10">
<x-ri-global-line class="size-5 text-primary" />
</div>
<h6 class="font-sans text-lg font-semibold leading-tight text-base">
Subdomain Management
</h6>
</div>
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
Create and manage DNS subdomains for your virtual machine. Each subdomain points to one of your assigned public IP addresses and is automatically configured with A and AAAA records.
</p>
<div class="pt-4 mt-4 border-t border-neutral/30 space-y-2.5">
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
<span class="font-semibold text-base/90">Limitations:</span> Only A and AAAA records are created. One subdomain per public IP address. Wildcard subdomains (*) are automatically created for each subdomain.
</p>
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
<span class="font-semibold text-base/90">DNS Propagation:</span> Changes may take a few minutes to propagate across DNS servers worldwide.
</p>
</div>
</div>
</div>
</div>
</div>
<p class="text-sm text-base/60 mt-1">Create and manage subdomains for dns.virtnet.host</p>
</div>
</div>
<!-- Disclaimer -->
<div class="bg-info/10 border border-info/30 rounded-lg p-4 mb-6"
x-data="{
isCollapsed: localStorage.getItem('subdomain-notice-collapsed') === 'true',
toggle() {
this.isCollapsed = !this.isCollapsed;
localStorage.setItem('subdomain-notice-collapsed', this.isCollapsed);
}
}">
<div class="flex items-start gap-3">
<x-ri-information-line class="size-5 text-info flex-shrink-0 mt-0.5" />
<div class="flex-1">
<div class="flex items-center justify-between mb-2">
<h3 class="text-sm font-semibold text-base">Subdomain Configuration Notice</h3>
<button @click="toggle()" type="button" class="text-base/60 hover:text-base transition-colors">
<x-ri-arrow-down-s-line x-bind:class="isCollapsed ? 'size-4 transition-transform rotate-180' : 'size-4 transition-transform'" />
</button>
</div>
<div x-show="!isCollapsed" x-transition>
<p class="text-xs text-base/70 leading-relaxed">
Only <strong>A and AAAA records</strong> are created. For any subdomain created, its child wildcard (*) subdomain is also automatically created. <strong>One subdomain per public IP address</strong> can be created. Under no circumstances will PTR, MX, NS, or other record types be added. These restrictions are in place to minimize abuse of the subdomain system.
</p>
</div>
</div>
</div>
</div>
<div class="flex flex-col gap-6">
<!-- Create Subdomain Section -->
<div class="bg-background-secondary hover:bg-background-secondary/80 border border-neutral rounded-lg p-6">
<div class="flex items-center justify-between mb-4 gap-4">
<div class="flex items-center gap-2 min-w-0">
<h3 class="text-lg font-semibold break-words">Create Subdomain</h3>
<div class="relative" x-data="{ open: false }" x-cloak>
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors">
<x-ri-question-line class="size-4" />
</button>
<!-- Modal Backdrop -->
<div x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@click="open = false"
@keydown.escape.window="open = false"
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
style="display: none;">
</div>
<!-- Modal -->
<div x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
@click.away="open = false"
@keydown.escape.window="open = false"
class="fixed inset-0 z-50 flex items-center justify-center p-4"
style="display: none;">
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
<x-ri-close-line class="size-5" />
</button>
<div class="flex items-center gap-3 mb-4 pr-8">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10">
<x-ri-add-circle-line class="size-5 text-primary" />
</div>
<h6 class="font-sans text-lg font-semibold leading-tight text-base">
Create Subdomain
</h6>
</div>
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
Create a new subdomain that points to one of your assigned public IP addresses. The subdomain will be created under the dns.virtnet.host domain.
</p>
<div class="pt-4 mt-4 border-t border-neutral/30 space-y-2.5">
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
<span class="font-semibold text-base/90">Subdomain Name:</span> Choose a unique name for your subdomain. Only alphanumeric characters and hyphens are allowed.
</p>
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
<span class="font-semibold text-base/90">IP Address:</span> Select which public IP address this subdomain should point to. Each IP can only have one subdomain.
</p>
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
A wildcard subdomain (*.yourname.dns.virtnet.host) will be automatically created along with your subdomain.
</p>
</div>
</div>
</div>
</div>
</div>
<x-button.primary wire:click="$set('showCreateModal', true)" :disabled="count($this->availableIpsForSubdomain) === 0" class="!w-auto flex-shrink-0">
<x-ri-add-circle-fill class="size-4" />
Create Subdomain
</x-button.primary>
</div>
@if(count($this->availableIpsForSubdomain) === 0)
<p class="text-sm text-base/60">All available public IP addresses already have subdomains assigned.</p>
@else
<p class="text-sm text-base/60">You can create {{ count($this->availableIpsForSubdomain) }} more subdomain(s).</p>
@endif
</div>
<!-- Existing Subdomains -->
<div class="bg-background-secondary hover:bg-background-secondary/80 border border-neutral rounded-lg p-6">
<div class="flex items-center gap-2 min-w-0 mb-4">
<h3 class="text-lg font-semibold break-words">Your Subdomains</h3>
<div class="relative" x-data="{ open: false }" x-cloak>
<button type="button" @click="open = true" class="text-base/50 hover:text-base/70 transition-colors">
<x-ri-question-line class="size-4" />
</button>
<!-- Modal Backdrop -->
<div x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
@click="open = false"
@keydown.escape.window="open = false"
class="fixed inset-0 z-50 bg-black/50 backdrop-blur-sm"
style="display: none;">
</div>
<!-- Modal -->
<div x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0 scale-95"
x-transition:enter-end="opacity-100 scale-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100 scale-100"
x-transition:leave-end="opacity-0 scale-95"
@click.away="open = false"
@keydown.escape.window="open = false"
class="fixed inset-0 z-50 flex items-center justify-center p-4"
style="display: none;">
<div class="relative w-full max-w-2xl max-h-[90vh] overflow-y-auto bg-background-secondary rounded-xl border border-neutral/20 shadow-2xl p-6">
<button @click="open = false" class="absolute top-4 right-4 text-base/50 hover:text-base transition-colors">
<x-ri-close-line class="size-5" />
</button>
<div class="flex items-center gap-3 mb-4 pr-8">
<div class="flex items-center justify-center w-10 h-10 rounded-lg bg-primary/10">
<x-ri-list-check class="size-5 text-primary" />
</div>
<h6 class="font-sans text-lg font-semibold leading-tight text-base">
Your Subdomains
</h6>
</div>
<p class="block font-sans text-sm font-normal leading-relaxed text-base/90 mb-4">
Lists all subdomains you have created for this virtual machine. Each subdomain shows its full domain name, associated IP address, and automatically created wildcard record.
</p>
<div class="pt-4 mt-4 border-t border-neutral/30 space-y-2.5">
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
<span class="font-semibold text-base/90">Delete:</span> Removes the subdomain and all associated DNS records. This action cannot be undone.
</p>
<p class="block font-sans text-xs font-normal leading-relaxed text-base/75">
<span class="font-semibold text-base/90">Wildcard Records:</span> Each subdomain automatically includes a wildcard (*) record that matches all subdomains under your chosen name.
</p>
</div>
</div>
</div>
</div>
</div>
@if(count($serverZones) === 0)
<p class="text-sm text-base/60">No subdomains created yet.</p>
@else
<div class="flex flex-col gap-4">
@foreach($serverZones as $zone)
<div class="bg-background border border-neutral rounded-lg p-4">
<div class="flex items-center justify-between">
<div>
<h4 class="text-md font-semibold">{{ $zone['subdomain'] }}.dns.virtnet.host</h4>
<p class="text-sm text-base/60">IP: {{ $zone['ipAddress'] }}</p>
@php
$hasWildcard = false;
$wildcardType = null;
if (isset($zone['records']) && is_array($zone['records'])) {
foreach ($zone['records'] as $record) {
if (($record['name'] ?? '') === '*') {
$hasWildcard = true;
$wildcardType = $record['type'] ?? 'A';
break;
}
}
}
@endphp
@if($hasWildcard)
<p class="text-xs text-base/50 mt-1">Wildcard: *.{{ $zone['subdomain'] }}.dns.virtnet.host ({{ $wildcardType }})</p>
@endif
</div>
<div class="flex gap-2">
<x-button.danger wire:click="openDeleteModal('{{ $zone['subdomain'] }}', {{ $zone['ipAddressId'] }})">
<x-ri-delete-bin-line class="size-4" />
Delete
</x-button.danger>
</div>
</div>
</div>
@endforeach
</div>
@endif
</div>
</div>
<!-- Create Subdomain Modal -->
@if($showCreateModal)
<div class="fixed inset-0 bg-black/50 flex items-center justify-center z-50" wire:click="$set('showCreateModal', false)">
<div class="bg-background border border-neutral rounded-lg p-6 max-w-md w-full mx-4" wire:click.stop>
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold">Create New Subdomain</h3>
<button wire:click="$set('showCreateModal', false)" class="text-base/60 hover:text-base">
<x-ri-close-line class="size-5" />
</button>
</div>
<div class="flex flex-col gap-4">
<div class="flex flex-col gap-2">
<div class="flex items-center justify-between gap-3">
<label class="text-sm font-semibold">Subdomain Name</label>
<button type="button"
wire:click="generateRandomSubdomain"
class="inline-flex items-center gap-1 text-xs font-semibold text-base/70 hover:text-base transition-colors">
<i class="ri-dice-line text-base"></i>
Random
</button>
</div>
<input type="text" wire:model.live="newSubdomain"
class="bg-background border border-neutral rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-secondary"
placeholder="myserver">
<p class="text-xs text-base/60">Will create: <span class="font-semibold">{{ $newSubdomain ? $newSubdomain . '.dns.virtnet.host' : 'subdomain.dns.virtnet.host' }}</span></p>
@error('newSubdomain')
<p class="text-xs text-error">{{ $message }}</p>
@enderror
</div>
<div class="flex flex-col gap-2">
<label class="text-sm font-semibold">IP Address</label>
<select wire:model="selectedIpId"
class="bg-background border border-neutral rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-secondary">
<option value="">Select an IP address</option>
@foreach($this->availableIpsForSubdomain as $ip)
<option value="{{ $ip->id }}">{{ $ip->ip }}</option>
@endforeach
</select>
@error('selectedIpId')
<p class="text-xs text-error">{{ $message }}</p>
@enderror
</div>
<div class="flex gap-2 justify-end">
<x-button.secondary wire:click="$set('showCreateModal', false)">Cancel</x-button.secondary>
<x-button.primary wire:click="createSubdomain" wire:loading.attr="disabled">
<x-loading target="createSubdomain" />
<span wire:loading.remove wire:target="createSubdomain">Create</span>
<span wire:loading wire:target="createSubdomain">Creating...</span>
</x-button.primary>
</div>
</div>
</div>
</div>
@endif
<!-- Manage Records Modal -->
@if($showRecordModal && $selectedZone)
<div class="fixed inset-0 bg-black/50 flex items-center justify-center z-50" wire:click="$set('showRecordModal', false)">
<div class="bg-background border border-neutral rounded-lg p-6 max-w-2xl w-full mx-4 max-h-[90vh] overflow-y-auto" wire:click.stop>
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold">DNS Records for {{ $selectedZone }}.dns.virtnet.host</h3>
<button wire:click="$set('showRecordModal', false)" class="text-base/60 hover:text-base">
<x-ri-close-line class="size-5" />
</button>
</div>
<!-- Add Record Form -->
<div class="bg-background-secondary border border-neutral rounded-lg p-4 mb-4">
<h4 class="text-md font-semibold mb-3">Add DNS Record</h4>
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold">Name (use @ for root, * for wildcard)</label>
<input type="text" wire:model="recordName"
class="bg-background border border-neutral rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-secondary"
placeholder="@ or www or *">
@error('recordName')
<p class="text-xs text-error">{{ $message }}</p>
@enderror
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold">Type</label>
<select wire:model="recordType"
class="bg-background border border-neutral rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-secondary">
<option value="A">A (IPv4)</option>
<option value="AAAA">AAAA (IPv6)</option>
</select>
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold">IP Address</label>
<input type="text" wire:model="recordRdata"
class="bg-background border border-neutral rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-secondary"
placeholder="1.2.3.4 or 2001:db8::1">
@error('recordRdata')
<p class="text-xs text-error">{{ $message }}</p>
@enderror
</div>
<div class="flex flex-col gap-2">
<label class="text-xs font-semibold">TTL (seconds)</label>
<input type="number" wire:model="recordTtl" min="60" max="86400"
class="bg-background border border-neutral rounded-md px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-secondary">
@error('recordTtl')
<p class="text-xs text-error">{{ $message }}</p>
@enderror
</div>
</div>
<div class="mt-3">
<x-button.primary wire:click="addRecord" wire:loading.attr="disabled" size="sm">
<x-loading target="addRecord" />
<span wire:loading.remove wire:target="addRecord">Add Record</span>
<span wire:loading wire:target="addRecord">Adding...</span>
</x-button.primary>
</div>
</div>
<!-- Existing Records -->
<div wire:key="zone-records-{{ $selectedZone }}">
<h4 class="text-md font-semibold mb-3">Existing Records</h4>
@if(empty($zoneRecords) || count($zoneRecords) === 0)
<p class="text-sm text-base/60">No DNS records found.</p>
@else
<div class="overflow-x-auto">
<table class="w-full">
<thead>
<tr class="border-b border-neutral">
<th class="text-left py-2 px-3 text-sm font-semibold">Name</th>
<th class="text-left py-2 px-3 text-sm font-semibold">Type</th>
<th class="text-left py-2 px-3 text-sm font-semibold">IP Address</th>
<th class="text-left py-2 px-3 text-sm font-semibold">TTL</th>
<th class="text-right py-2 px-3 text-sm font-semibold">Actions</th>
</tr>
</thead>
<tbody>
@foreach($zoneRecords as $index => $record)
<tr class="border-b border-neutral/40" wire:key="record-{{ $selectedZone }}-{{ $index }}">
<td class="py-2 px-3 text-sm">{{ $record['name'] ?? '@' }}</td>
<td class="py-2 px-3 text-sm">{{ $record['type'] ?? 'A' }}</td>
<td class="py-2 px-3 text-sm">{{ $record['rdata'] ?? $record['ipAddress'] ?? '' }}</td>
<td class="py-2 px-3 text-sm">{{ $record['ttl'] ?? 3600 }}</td>
<td class="py-2 px-3 text-sm text-right">
<x-button.danger wire:click="deleteRecord('{{ $record['name'] ?? '@' }}', '{{ $record['type'] ?? 'A' }}')"
wire:confirm="Are you sure you want to delete this record?" size="sm">
<x-ri-delete-bin-line class="size-4" />
</x-button.danger>
</td>
</tr>
@endforeach
</tbody>
</table>
</div>
@endif
</div>
</div>
</div>
@endif
<!-- Delete Subdomain Confirmation Modal -->
@if($showDeleteModal && $subdomainToDelete)
<div class="fixed inset-0 bg-black/50 flex items-center justify-center z-50" wire:click="closeDeleteModal">
<div class="bg-background border border-neutral rounded-lg p-6 max-w-md w-full mx-4" wire:click.stop>
<div class="flex items-center justify-between mb-4">
<h3 class="text-lg font-semibold">Delete Subdomain</h3>
<button wire:click="closeDeleteModal" class="text-base/60 hover:text-base">
<x-ri-close-line class="size-5" />
</button>
</div>
<div class="mb-6">
<p class="text-sm text-base/60 mb-2">Are you sure you want to delete the following subdomain?</p>
<p class="text-md font-semibold">{{ $subdomainToDeleteFull }}</p>
<p class="text-xs text-base/60 mt-2">This action cannot be undone. All DNS records associated with this subdomain will be deleted.</p>
</div>
<div class="flex gap-2 justify-end">
<x-button.secondary wire:click="closeDeleteModal">Cancel</x-button.secondary>
<x-button.danger wire:click="deleteSubdomain" wire:loading.attr="disabled">
<x-loading target="deleteSubdomain" />
<span wire:loading.remove wire:target="deleteSubdomain">Delete</span>
<span wire:loading wire:target="deleteSubdomain">Deleting...</span>
</x-button.danger>
</div>
</div>
</div>
@endif
</x-proxmox::layout>