Initial Commit

This commit is contained in:
Hosteroid
2025-10-08 14:23:07 +03:00
commit b3b3ac66ff
78 changed files with 14248 additions and 0 deletions

View File

@@ -0,0 +1,128 @@
<?php
$title = 'Bulk Add Domains';
$pageTitle = 'Bulk Add Domains';
$pageDescription = 'Add multiple domains at once';
$pageIcon = 'fas fa-layer-group';
ob_start();
?>
<!-- Main Form -->
<div class="max-w-4xl mx-auto">
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200">
<h2 class="text-lg font-semibold text-gray-900 flex items-center">
<i class="fas fa-layer-group text-gray-400 mr-2 text-sm"></i>
Bulk Add Domains
</h2>
</div>
<div class="p-6">
<form method="POST" action="/domains/bulk-add" class="space-y-5">
<!-- Domains Textarea -->
<div>
<label for="domains" class="block text-sm font-medium text-gray-700 mb-1.5">
Domain Names *
</label>
<textarea
id="domains"
name="domains"
rows="10"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm font-mono"
placeholder="example.com&#10;google.com&#10;github.com&#10;..."
required
autofocus></textarea>
<p class="mt-1.5 text-xs text-gray-500">
Enter one domain per line. Domains without http:// or www.
</p>
</div>
<!-- Notification Group -->
<div>
<label for="notification_group_id" class="block text-sm font-medium text-gray-700 mb-1.5">
Notification Group (Optional)
</label>
<select id="notification_group_id"
name="notification_group_id"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm">
<option value="">-- No Group (No notifications) --</option>
<?php foreach ($groups as $group): ?>
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
<?php endforeach; ?>
</select>
<p class="mt-1.5 text-xs text-gray-500">
Assign all domains to this notification group
</p>
</div>
<!-- Action Buttons -->
<div class="flex flex-col sm:flex-row gap-3 pt-3">
<button type="submit"
class="inline-flex items-center justify-center px-5 py-2.5 bg-primary hover:bg-primary-dark text-white rounded-lg font-medium transition-colors text-sm">
<i class="fas fa-plus-circle mr-2"></i>
Add All Domains
</button>
<a href="/domains"
class="inline-flex items-center justify-center px-5 py-2.5 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors text-sm">
<i class="fas fa-times mr-2"></i>
Cancel
</a>
</div>
</form>
</div>
</div>
<!-- Info Cards -->
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- How it works -->
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
<div class="w-10 h-10 bg-blue-500 rounded-lg flex items-center justify-center">
<i class="fas fa-info-circle text-white"></i>
</div>
</div>
<div class="ml-3">
<h3 class="text-sm font-semibold text-gray-900 mb-1">How It Works</h3>
<p class="text-xs text-gray-600 leading-relaxed">
Paste multiple domain names, one per line. The system will fetch WHOIS information
for each domain automatically. This may take a few moments depending on how many domains you're adding.
</p>
</div>
</div>
</div>
<!-- Important notes -->
<div class="bg-orange-50 border border-orange-200 rounded-lg p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
<div class="w-10 h-10 bg-orange-500 rounded-lg flex items-center justify-center">
<i class="fas fa-exclamation-triangle text-white"></i>
</div>
</div>
<div class="ml-3">
<h3 class="text-sm font-semibold text-gray-900 mb-1">Important Notes</h3>
<ul class="text-xs text-gray-600 space-y-1">
<li class="flex items-start">
<i class="fas fa-circle text-orange-500 mt-1 mr-2" style="font-size: 6px;"></i>
<span>Duplicate domains will be skipped</span>
</li>
<li class="flex items-start">
<i class="fas fa-circle text-orange-500 mt-1 mr-2" style="font-size: 6px;"></i>
<span>Invalid domains will be reported</span>
</li>
<li class="flex items-start">
<i class="fas fa-circle text-orange-500 mt-1 mr-2" style="font-size: 6px;"></i>
<span>Large batches may take several minutes</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<?php
$content = ob_get_clean();
include __DIR__ . '/../layout/base.php';
?>

View File

@@ -0,0 +1,130 @@
<?php
$title = 'Add New Domain';
$pageTitle = 'Add New Domain';
$pageDescription = 'Start monitoring a new domain';
$pageIcon = 'fas fa-plus-circle';
ob_start();
?>
<!-- Main Form -->
<div class="max-w-3xl mx-auto">
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200">
<h2 class="text-lg font-semibold text-gray-900 flex items-center">
<i class="fas fa-globe text-gray-400 mr-2 text-sm"></i>
Domain Information
</h2>
</div>
<div class="p-6">
<form method="POST" action="/domains/store" class="space-y-5">
<!-- Domain Name -->
<div>
<label for="domain_name" class="block text-sm font-medium text-gray-700 mb-1.5">
Domain Name *
</label>
<input type="text"
id="domain_name"
name="domain_name"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm"
placeholder="example.com"
required
autofocus>
<p class="mt-1.5 text-xs text-gray-500">
Enter the domain name without http:// or https://
</p>
</div>
<!-- Notification Group -->
<div>
<label for="notification_group_id" class="block text-sm font-medium text-gray-700 mb-1.5">
Notification Group
</label>
<select id="notification_group_id"
name="notification_group_id"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm">
<option value="">-- No Group (No notifications) --</option>
<?php foreach ($groups as $group): ?>
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
<?php endforeach; ?>
</select>
<p class="mt-1.5 text-xs text-gray-500">
Optional: Assign to a notification group to receive expiry alerts
</p>
</div>
<!-- Action Buttons -->
<div class="flex flex-col sm:flex-row gap-3 pt-3">
<button type="submit"
class="inline-flex items-center justify-center px-5 py-2.5 bg-primary hover:bg-primary-dark text-white rounded-lg font-medium transition-colors text-sm">
<i class="fas fa-plus-circle mr-2"></i>
Add Domain
</button>
<a href="/domains"
class="inline-flex items-center justify-center px-5 py-2.5 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors text-sm">
<i class="fas fa-times mr-2"></i>
Cancel
</a>
</div>
</form>
</div>
</div>
<!-- Info Cards -->
<div class="mt-4 grid grid-cols-1 md:grid-cols-2 gap-4">
<!-- How it works -->
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
<div class="w-10 h-10 bg-blue-500 rounded-lg flex items-center justify-center">
<i class="fas fa-info-circle text-white"></i>
</div>
</div>
<div class="ml-3">
<h3 class="text-sm font-semibold text-gray-900 mb-1">How It Works</h3>
<p class="text-xs text-gray-600 leading-relaxed">
When you add a domain, we automatically fetch its WHOIS information including
expiration date, registrar, nameservers, and other important details. This may take a few seconds.
</p>
</div>
</div>
</div>
<!-- What we track -->
<div class="bg-green-50 border border-green-200 rounded-lg p-4">
<div class="flex items-start">
<div class="flex-shrink-0">
<div class="w-10 h-10 bg-green-500 rounded-lg flex items-center justify-center">
<i class="fas fa-check-circle text-white"></i>
</div>
</div>
<div class="ml-3">
<h3 class="text-sm font-semibold text-gray-900 mb-1">What We Track</h3>
<ul class="text-xs text-gray-600 space-y-1">
<li class="flex items-center">
<i class="fas fa-circle text-green-500" style="font-size: 6px;"></i>
<span class="ml-2">Domain expiration date</span>
</li>
<li class="flex items-center">
<i class="fas fa-circle text-green-500" style="font-size: 6px;"></i>
<span class="ml-2">Registrar information</span>
</li>
<li class="flex items-center">
<i class="fas fa-circle text-green-500" style="font-size: 6px;"></i>
<span class="ml-2">Nameservers</span>
</li>
<li class="flex items-center">
<i class="fas fa-circle text-green-500" style="font-size: 6px;"></i>
<span class="ml-2">Domain status</span>
</li>
</ul>
</div>
</div>
</div>
</div>
</div>
<?php
$content = ob_get_clean();
include __DIR__ . '/../layout/base.php';
?>

120
app/Views/domains/edit.php Normal file
View File

@@ -0,0 +1,120 @@
<?php
$title = 'Edit Domain';
$pageTitle = 'Edit Domain';
$pageDescription = htmlspecialchars($domain['domain_name']);
$pageIcon = 'fas fa-edit';
ob_start();
?>
<!-- Main Form -->
<div class="max-w-3xl mx-auto">
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div class="px-6 py-4 border-b border-gray-200">
<h2 class="text-lg font-semibold text-gray-900 flex items-center">
<i class="fas fa-cog text-gray-400 mr-2 text-sm"></i>
Domain Settings
</h2>
</div>
<div class="p-6">
<form method="POST" action="/domains/<?= $domain['id'] ?>/update" class="space-y-5">
<!-- Domain Name (Read-only) -->
<div>
<label class="block text-sm font-medium text-gray-700 mb-1.5">
Domain Name
</label>
<div class="relative">
<input type="text"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg bg-gray-50 text-gray-600 cursor-not-allowed text-sm"
value="<?= htmlspecialchars($domain['domain_name']) ?>"
disabled>
<div class="absolute right-3 top-1/2 transform -translate-y-1/2">
<i class="fas fa-lock text-gray-400 text-xs"></i>
</div>
</div>
<p class="mt-1.5 text-xs text-gray-500">
Domain name cannot be changed after creation
</p>
</div>
<!-- Notification Group -->
<div>
<label for="notification_group_id" class="block text-sm font-medium text-gray-700 mb-1.5">
Notification Group
</label>
<select id="notification_group_id"
name="notification_group_id"
class="w-full px-3 py-2.5 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary transition-colors text-sm">
<option value="">-- No Group (No notifications) --</option>
<?php foreach ($groups as $group): ?>
<option value="<?= $group['id'] ?>"
<?= $domain['notification_group_id'] == $group['id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($group['name']) ?>
</option>
<?php endforeach; ?>
</select>
<p class="mt-1.5 text-xs text-gray-500">
Change the notification group or remove it to stop receiving alerts
</p>
</div>
<!-- Active Monitoring -->
<div class="bg-gray-50 rounded-lg p-4 border border-gray-200">
<label class="flex items-center cursor-pointer">
<input type="checkbox"
name="is_active"
<?= $domain['is_active'] ? 'checked' : '' ?>
class="w-4 h-4 text-primary border-gray-300 rounded focus:ring-primary cursor-pointer">
<div class="ml-3">
<span class="text-sm font-medium text-gray-900">Enable Active Monitoring</span>
<p class="text-xs text-gray-600 mt-0.5">When enabled, this domain will be checked regularly and notifications will be sent</p>
</div>
</label>
</div>
<!-- Action Buttons -->
<div class="flex flex-col sm:flex-row gap-3 pt-3">
<button type="submit"
class="inline-flex items-center justify-center px-5 py-2.5 bg-primary hover:bg-primary-dark text-white rounded-lg font-medium transition-colors text-sm">
<i class="fas fa-save mr-2"></i>
Update Domain
</button>
<a href="/domains/<?= $domain['id'] ?>"
class="inline-flex items-center justify-center px-5 py-2.5 border border-gray-300 text-gray-700 rounded-lg font-medium hover:bg-gray-50 transition-colors text-sm">
<i class="fas fa-times mr-2"></i>
Cancel
</a>
</div>
</form>
</div>
</div>
<!-- Quick Actions -->
<div class="mt-4 grid grid-cols-1 md:grid-cols-3 gap-3">
<a href="/domains/<?= $domain['id'] ?>"
class="flex items-center justify-center p-3 bg-white border border-gray-200 rounded-lg hover:border-blue-300 hover:bg-blue-50 transition-colors group">
<i class="fas fa-eye text-blue-600 mr-2 text-sm"></i>
<span class="text-sm font-medium text-gray-700">View Details</span>
</a>
<form method="POST" action="/domains/<?= $domain['id'] ?>/refresh" class="m-0">
<button type="submit"
class="w-full flex items-center justify-center p-3 bg-white border border-gray-200 rounded-lg hover:border-green-300 hover:bg-green-50 transition-colors group">
<i class="fas fa-sync-alt text-green-600 mr-2 text-sm"></i>
<span class="text-sm font-medium text-gray-700">Refresh WHOIS</span>
</button>
</form>
<form method="POST" action="/domains/<?= $domain['id'] ?>/delete" onsubmit="return confirm('Delete this domain permanently?')" class="m-0">
<button type="submit"
class="w-full flex items-center justify-center p-3 bg-white border border-gray-200 rounded-lg hover:border-red-300 hover:bg-red-50 transition-colors group">
<i class="fas fa-trash text-red-600 mr-2 text-sm"></i>
<span class="text-sm font-medium text-gray-700">Delete Domain</span>
</button>
</form>
</div>
</div>
<?php
$content = ob_get_clean();
include __DIR__ . '/../layout/base.php';
?>

688
app/Views/domains/index.php Normal file
View File

@@ -0,0 +1,688 @@
<?php
$title = 'Domains';
$pageTitle = 'Domain Management';
$pageDescription = 'Monitor and manage your domain portfolio';
$pageIcon = 'fas fa-globe';
ob_start();
// Helper function to generate sort URL
function sortUrl($column, $currentSort, $currentOrder) {
$newOrder = ($currentSort === $column && $currentOrder === 'asc') ? 'desc' : 'asc';
$params = $_GET;
$params['sort'] = $column;
$params['order'] = $newOrder;
return '/domains?' . http_build_query($params);
}
// Helper function for sort icon
function sortIcon($column, $currentSort, $currentOrder) {
if ($currentSort !== $column) {
return '<i class="fas fa-sort text-gray-400 ml-1 text-xs"></i>';
}
$icon = $currentOrder === 'asc' ? 'fa-sort-up' : 'fa-sort-down';
return '<i class="fas ' . $icon . ' text-primary ml-1 text-xs"></i>';
}
// Get current filters
$currentFilters = $filters ?? ['search' => '', 'status' => '', 'group' => '', 'sort' => 'domain_name', 'order' => 'asc'];
?>
<!-- Action Buttons -->
<div class="mb-4 flex flex-wrap gap-2 justify-between items-center">
<div class="flex gap-2">
<!-- Bulk Actions Toolbar (Hidden by default, shown when domains are selected) -->
<div id="bulk-actions" class="hidden items-center gap-2">
<span id="selected-count" class="text-sm font-medium text-gray-700"></span>
<button type="button" onclick="bulkRefresh()" class="inline-flex items-center px-4 py-2 bg-green-600 text-white text-sm rounded-lg hover:bg-green-700 transition-colors font-medium">
<i class="fas fa-sync-alt mr-2"></i>
Refresh
</button>
<div class="relative inline-block">
<button type="button" onclick="toggleAssignGroupDropdown()" class="inline-flex items-center px-4 py-2 bg-blue-600 text-white text-sm rounded-lg hover:bg-blue-700 transition-colors font-medium">
<i class="fas fa-bell mr-2"></i>
Assign Group
<i class="fas fa-chevron-down ml-2 text-xs"></i>
</button>
<div id="assign-group-dropdown" class="hidden absolute left-0 mt-2 w-64 bg-white rounded-lg shadow-lg border border-gray-200 z-10">
<form method="POST" action="/domains/bulk-assign-group" id="bulk-assign-form">
<input type="hidden" name="domain_ids" id="bulk-assign-ids">
<div class="p-3">
<select name="group_id" class="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm">
<option value="">-- No Group --</option>
<?php foreach ($groups as $group): ?>
<option value="<?= $group['id'] ?>"><?= htmlspecialchars($group['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="border-t border-gray-200 p-2 flex gap-2">
<button type="submit" class="flex-1 px-3 py-1.5 bg-primary text-white text-xs rounded hover:bg-primary-dark">
Assign
</button>
<button type="button" onclick="toggleAssignGroupDropdown()" class="flex-1 px-3 py-1.5 bg-gray-200 text-gray-700 text-xs rounded hover:bg-gray-300">
Cancel
</button>
</div>
</form>
</div>
</div>
<button type="button" onclick="bulkDelete()" class="inline-flex items-center px-4 py-2 bg-red-600 text-white text-sm rounded-lg hover:bg-red-700 transition-colors font-medium">
<i class="fas fa-trash mr-2"></i>
Delete
</button>
<button type="button" onclick="clearSelection()" class="inline-flex items-center px-4 py-2 border border-gray-300 text-gray-700 text-sm rounded-lg hover:bg-gray-50 transition-colors font-medium">
<i class="fas fa-times mr-2"></i>
Clear
</button>
</div>
</div>
<div class="flex gap-2">
<?php if (!empty($domains)): ?>
<form method="POST" action="/domains/bulk-refresh" id="refresh-all-form">
<?php foreach ($domains as $domain): ?>
<input type="hidden" name="domain_ids[]" value="<?= $domain['id'] ?>">
<?php endforeach; ?>
<button type="submit" class="inline-flex items-center px-4 py-2 bg-green-600 text-white text-sm rounded-lg hover:bg-green-700 transition-colors font-medium" title="Refresh all domains on this page">
<i class="fas fa-sync-alt mr-2"></i>
Refresh Page (<?= count($domains) ?>)
</button>
</form>
<?php endif; ?>
<a href="/domains/bulk-add" class="inline-flex items-center px-4 py-2 bg-purple-600 text-white text-sm rounded-lg hover:bg-purple-700 transition-colors font-medium">
<i class="fas fa-layer-group mr-2"></i>
Bulk Add
</a>
<a href="/domains/create" class="inline-flex items-center px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors text-sm font-medium">
<i class="fas fa-plus mr-2"></i>
Add Domain
</a>
</div>
</div>
<!-- Filters & Search -->
<div class="bg-white rounded-lg border border-gray-200 p-5 mb-4">
<form method="GET" action="/domains" id="filter-form">
<div class="grid grid-cols-1 md:grid-cols-4 gap-3">
<div>
<label class="block text-xs font-medium text-gray-700 mb-1.5">Search</label>
<div class="relative">
<input type="text" name="search" id="domainSearch" value="<?= htmlspecialchars($currentFilters['search']) ?>" placeholder="Search domains..." class="w-full pl-9 pr-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary text-sm">
<i class="fas fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 text-xs"></i>
</div>
</div>
<div>
<label class="block text-xs font-medium text-gray-700 mb-1.5">Status</label>
<select name="status" id="statusFilter" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary text-sm">
<option value="">All Statuses</option>
<option value="active" <?= $currentFilters['status'] === 'active' ? 'selected' : '' ?>>Active</option>
<option value="expiring_soon" <?= $currentFilters['status'] === 'expiring_soon' ? 'selected' : '' ?>>Expiring Soon</option>
<option value="inactive" <?= $currentFilters['status'] === 'inactive' ? 'selected' : '' ?>>Inactive</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-700 mb-1.5">Group</label>
<select name="group" id="groupFilter" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-primary focus:border-primary text-sm">
<option value="">All Groups</option>
<?php foreach ($groups as $group): ?>
<option value="<?= $group['id'] ?>" <?= $currentFilters['group'] == $group['id'] ? 'selected' : '' ?>><?= htmlspecialchars($group['name']) ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="flex items-end">
<button type="submit" class="w-full px-4 py-2 bg-primary text-white rounded-lg hover:bg-primary-dark transition-colors text-sm font-medium">
<i class="fas fa-filter mr-2"></i>
Apply Filters
</button>
</div>
</div>
<input type="hidden" name="sort" value="<?= htmlspecialchars($currentFilters['sort']) ?>">
<input type="hidden" name="order" value="<?= htmlspecialchars($currentFilters['order']) ?>">
</form>
</div>
<!-- Pagination Info & Per Page Selector -->
<div class="mb-4 flex justify-between items-center">
<div class="text-sm text-gray-600">
Showing <span class="font-semibold text-gray-900"><?= $pagination['showing_from'] ?></span> to
<span class="font-semibold text-gray-900"><?= $pagination['showing_to'] ?></span> of
<span class="font-semibold text-gray-900"><?= $pagination['total'] ?></span> domain(s)
</div>
<form method="GET" action="/domains" class="flex items-center gap-2">
<!-- Preserve current filters -->
<input type="hidden" name="search" value="<?= htmlspecialchars($currentFilters['search']) ?>">
<input type="hidden" name="status" value="<?= htmlspecialchars($currentFilters['status']) ?>">
<input type="hidden" name="group" value="<?= htmlspecialchars($currentFilters['group']) ?>">
<input type="hidden" name="sort" value="<?= htmlspecialchars($currentFilters['sort']) ?>">
<input type="hidden" name="order" value="<?= htmlspecialchars($currentFilters['order']) ?>">
<label for="per_page" class="text-sm text-gray-600">Show:</label>
<select name="per_page" id="per_page" onchange="this.form.submit()" class="px-3 py-1.5 border border-gray-300 rounded-lg text-sm focus:ring-2 focus:ring-primary focus:border-primary">
<option value="10" <?= $pagination['per_page'] == 10 ? 'selected' : '' ?>>10</option>
<option value="25" <?= $pagination['per_page'] == 25 ? 'selected' : '' ?>>25</option>
<option value="50" <?= $pagination['per_page'] == 50 ? 'selected' : '' ?>>50</option>
<option value="100" <?= $pagination['per_page'] == 100 ? 'selected' : '' ?>>100</option>
</select>
</form>
</div>
<!-- Domains List -->
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<?php if (!empty($domains)): ?>
<!-- Table View (Desktop) -->
<div class="hidden lg:block overflow-x-auto">
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-3 text-left w-12">
<input type="checkbox" id="select-all" onclick="toggleSelectAll(this)" class="w-4 h-4 text-primary border-gray-300 rounded focus:ring-primary cursor-pointer">
</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
<a href="<?= sortUrl('domain_name', $currentFilters['sort'], $currentFilters['order']) ?>" class="hover:text-primary flex items-center">
Domain <?= sortIcon('domain_name', $currentFilters['sort'], $currentFilters['order']) ?>
</a>
</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
<a href="<?= sortUrl('registrar', $currentFilters['sort'], $currentFilters['order']) ?>" class="hover:text-primary flex items-center">
Registrar <?= sortIcon('registrar', $currentFilters['sort'], $currentFilters['order']) ?>
</a>
</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
<a href="<?= sortUrl('expiration_date', $currentFilters['sort'], $currentFilters['order']) ?>" class="hover:text-primary flex items-center">
Expiration <?= sortIcon('expiration_date', $currentFilters['sort'], $currentFilters['order']) ?>
</a>
</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
<a href="<?= sortUrl('status', $currentFilters['sort'], $currentFilters['order']) ?>" class="hover:text-primary flex items-center">
Status <?= sortIcon('status', $currentFilters['sort'], $currentFilters['order']) ?>
</a>
</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
<a href="<?= sortUrl('group_name', $currentFilters['sort'], $currentFilters['order']) ?>" class="hover:text-primary flex items-center">
Group <?= sortIcon('group_name', $currentFilters['sort'], $currentFilters['order']) ?>
</a>
</th>
<th class="px-6 py-3 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">
<a href="<?= sortUrl('last_checked', $currentFilters['sort'], $currentFilters['order']) ?>" class="hover:text-primary flex items-center">
Last Checked <?= sortIcon('last_checked', $currentFilters['sort'], $currentFilters['order']) ?>
</a>
</th>
<th class="px-6 py-3 text-right text-xs font-semibold text-gray-600 uppercase tracking-wider">Actions</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<?php foreach ($domains as $domain): ?>
<?php
// Calculate days until expiry and determine status color
$daysLeft = !empty($domain['expiration_date']) ? floor((strtotime($domain['expiration_date']) - time()) / 86400) : null;
$expiryClass = '';
if ($daysLeft !== null) {
if ($daysLeft < 0) {
$expiryClass = 'text-red-600 font-semibold';
} elseif ($daysLeft <= 30) {
$expiryClass = 'text-orange-600 font-semibold';
} elseif ($daysLeft <= 90) {
$expiryClass = 'text-yellow-600';
}
}
// Recalculate domain status if it's empty or error (for backward compatibility)
$domainStatus = $domain['status'];
if (empty($domainStatus) || $domainStatus === 'error') {
$whoisData = json_decode($domain['whois_data'] ?? '{}', true);
$statusArray = $whoisData['status'] ?? [];
$isAvailable = false;
foreach ($statusArray as $status) {
if (stripos($status, 'AVAILABLE') !== false || stripos($status, 'FREE') !== false) {
$isAvailable = true;
break;
}
}
if ($isAvailable) {
$domainStatus = 'available';
} elseif ($daysLeft !== null) {
if ($daysLeft < 0) {
$domainStatus = 'expired';
} elseif ($daysLeft <= 30) {
$domainStatus = 'expiring_soon';
} else {
$domainStatus = 'active';
}
} else {
$domainStatus = 'error';
}
}
// Status badge color
if ($domainStatus === 'available') {
$statusClass = 'bg-blue-100 text-blue-700 border-blue-200';
$statusText = 'Available';
$statusIcon = 'fa-info-circle';
} elseif ($daysLeft !== null && $daysLeft <= 30 && $daysLeft >= 0) {
$statusClass = 'bg-orange-100 text-orange-700 border-orange-200';
$statusText = 'Expiring Soon';
$statusIcon = 'fa-exclamation-triangle';
} elseif ($domainStatus === 'active') {
$statusClass = 'bg-green-100 text-green-700 border-green-200';
$statusText = 'Active';
$statusIcon = 'fa-check-circle';
} elseif ($domainStatus === 'expired') {
$statusClass = 'bg-red-100 text-red-700 border-red-200';
$statusText = 'Expired';
$statusIcon = 'fa-times-circle';
} elseif ($domainStatus === 'error') {
$statusClass = 'bg-gray-100 text-gray-700 border-gray-200';
$statusText = 'Error';
$statusIcon = 'fa-exclamation-circle';
} else {
$statusClass = 'bg-gray-100 text-gray-700 border-gray-200';
$statusText = ucfirst($domainStatus);
$statusIcon = 'fa-times-circle';
}
?>
<tr class="hover:bg-gray-50 transition-colors duration-150 domain-row">
<td class="px-4 py-4">
<input type="checkbox" class="domain-checkbox w-4 h-4 text-primary border-gray-300 rounded focus:ring-primary cursor-pointer" value="<?= $domain['id'] ?>">
</td>
<td class="px-6 py-4 whitespace-nowrap">
<div class="flex items-center">
<div class="flex-shrink-0 h-10 w-10 bg-primary bg-opacity-10 rounded-lg flex items-center justify-center">
<i class="fas fa-globe text-primary"></i>
</div>
<div class="ml-4">
<a href="/domains/<?= $domain['id'] ?>" class="text-sm font-semibold text-gray-900 hover:text-primary"><?= htmlspecialchars($domain['domain_name']) ?></a>
<?php if (!empty($domain['nameservers'])): ?>
<div class="text-xs text-gray-500">NS: <?= htmlspecialchars(explode(',', $domain['nameservers'])[0]) ?></div>
<?php endif; ?>
</div>
</div>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<?php if (!empty($domain['registrar'])): ?>
<div class="flex items-center">
<i class="fas fa-building text-gray-400 mr-2"></i>
<span class="text-sm text-gray-900"><?= htmlspecialchars($domain['registrar']) ?></span>
</div>
<?php else: ?>
<span class="text-sm text-gray-400">Unknown</span>
<?php endif; ?>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<?php if (!empty($domain['expiration_date'])): ?>
<div class="text-sm">
<div class="font-medium text-gray-900"><?= date('M d, Y', strtotime($domain['expiration_date'])) ?></div>
<div class="text-xs <?= $expiryClass ?>">
<?= $daysLeft ?> days
</div>
</div>
<?php else: ?>
<span class="text-sm text-gray-400">Not set</span>
<?php endif; ?>
</td>
<td class="px-6 py-4 whitespace-nowrap">
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-semibold border <?= $statusClass ?>">
<i class="fas <?= $statusIcon ?> mr-1"></i>
<?= $statusText ?>
</span>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900">
<?php if (!empty($domain['group_name'])): ?>
<span class="inline-flex items-center px-3 py-1 rounded-full text-xs font-medium bg-blue-100 text-blue-800">
<i class="fas fa-bell mr-1"></i>
<?= htmlspecialchars($domain['group_name']) ?>
</span>
<?php else: ?>
<span class="text-gray-400">No Group</span>
<?php endif; ?>
</td>
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
<?php if (!empty($domain['last_checked'])): ?>
<div class="flex items-center">
<i class="far fa-clock mr-2"></i>
<?= date('M d, H:i', strtotime($domain['last_checked'])) ?>
</div>
<?php else: ?>
<span class="text-gray-400">Never</span>
<?php endif; ?>
</td>
<td class="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
<div class="flex items-center justify-end space-x-2">
<a href="/domains/<?= $domain['id'] ?>" class="text-blue-600 hover:text-blue-800" title="View">
<i class="fas fa-eye"></i>
</a>
<form method="POST" action="/domains/<?= $domain['id'] ?>/refresh" class="inline">
<button type="submit" class="text-green-600 hover:text-green-800" title="Refresh WHOIS">
<i class="fas fa-sync-alt"></i>
</button>
</form>
<a href="/domains/<?= $domain['id'] ?>/edit" class="text-yellow-600 hover:text-yellow-800" title="Edit">
<i class="fas fa-edit"></i>
</a>
<form method="POST" action="/domains/<?= $domain['id'] ?>/delete" class="inline" onsubmit="return confirm('Delete this domain?')">
<button type="submit" class="text-red-600 hover:text-red-800" title="Delete">
<i class="fas fa-trash"></i>
</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- Card View (Mobile) - Simplified for brevity -->
<div class="lg:hidden divide-y divide-gray-200">
<?php foreach ($domains as $domain): ?>
<div class="p-6 hover:bg-gray-50 transition-colors duration-150">
<div class="flex items-center mb-3">
<input type="checkbox" class="domain-checkbox-mobile w-4 h-4 text-primary border-gray-300 rounded focus:ring-primary cursor-pointer mr-3" value="<?= $domain['id'] ?>">
<a href="/domains/<?= $domain['id'] ?>" class="text-lg font-semibold text-gray-900 hover:text-primary"><?= htmlspecialchars($domain['domain_name']) ?></a>
</div>
<!-- Add mobile view content here if needed -->
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<div class="text-center py-12 px-6">
<div class="mb-4">
<i class="fas fa-globe text-gray-300 text-6xl"></i>
</div>
<h3 class="text-lg font-semibold text-gray-700 mb-1">No Domains Yet</h3>
<p class="text-sm text-gray-500 mb-4">Start monitoring your domains by adding your first one</p>
<a href="/domains/create" class="inline-flex items-center px-5 py-2.5 bg-primary text-white text-sm rounded-lg hover:bg-primary-dark transition-colors font-medium">
<i class="fas fa-plus mr-2"></i>
<span>Add Your First Domain</span>
</a>
</div>
<?php endif; ?>
</div>
<!-- Pagination Controls -->
<?php if ($pagination['total_pages'] > 1): ?>
<div class="mt-4 flex flex-col sm:flex-row items-center justify-between gap-4">
<!-- Page Info -->
<div class="text-sm text-gray-600">
Page <span class="font-semibold text-gray-900"><?= $pagination['current_page'] ?></span> of
<span class="font-semibold text-gray-900"><?= $pagination['total_pages'] ?></span>
</div>
<!-- Pagination Buttons -->
<div class="flex items-center gap-1">
<?php
$currentPage = $pagination['current_page'];
$totalPages = $pagination['total_pages'];
// Helper function to build pagination URL
function paginationUrl($page, $filters, $perPage) {
$params = $filters;
$params['page'] = $page;
$params['per_page'] = $perPage;
return '/domains?' . http_build_query($params);
}
?>
<!-- First Page -->
<?php if ($currentPage > 1): ?>
<a href="<?= paginationUrl(1, $currentFilters, $pagination['per_page']) ?>" class="px-3 py-2 text-sm border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors">
<i class="fas fa-angle-double-left"></i>
</a>
<?php endif; ?>
<!-- Previous Page -->
<?php if ($currentPage > 1): ?>
<a href="<?= paginationUrl($currentPage - 1, $currentFilters, $pagination['per_page']) ?>" class="px-3 py-2 text-sm border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors">
<i class="fas fa-angle-left"></i> Previous
</a>
<?php endif; ?>
<!-- Page Numbers -->
<?php
$range = 2; // Show 2 pages on each side of current page
$start = max(1, $currentPage - $range);
$end = min($totalPages, $currentPage + $range);
// Show first page + ellipsis if needed
if ($start > 1) {
echo '<a href="' . paginationUrl(1, $currentFilters, $pagination['per_page']) . '" class="px-3 py-2 text-sm border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors">1</a>';
if ($start > 2) {
echo '<span class="px-2 text-gray-500">...</span>';
}
}
// Page numbers
for ($i = $start; $i <= $end; $i++) {
if ($i == $currentPage) {
echo '<span class="px-3 py-2 text-sm bg-primary text-white rounded-lg font-semibold">' . $i . '</span>';
} else {
echo '<a href="' . paginationUrl($i, $currentFilters, $pagination['per_page']) . '" class="px-3 py-2 text-sm border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors">' . $i . '</a>';
}
}
// Show last page + ellipsis if needed
if ($end < $totalPages) {
if ($end < $totalPages - 1) {
echo '<span class="px-2 text-gray-500">...</span>';
}
echo '<a href="' . paginationUrl($totalPages, $currentFilters, $pagination['per_page']) . '" class="px-3 py-2 text-sm border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors">' . $totalPages . '</a>';
}
?>
<!-- Next Page -->
<?php if ($currentPage < $totalPages): ?>
<a href="<?= paginationUrl($currentPage + 1, $currentFilters, $pagination['per_page']) ?>" class="px-3 py-2 text-sm border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors">
Next <i class="fas fa-angle-right"></i>
</a>
<?php endif; ?>
<!-- Last Page -->
<?php if ($currentPage < $totalPages): ?>
<a href="<?= paginationUrl($totalPages, $currentFilters, $pagination['per_page']) ?>" class="px-3 py-2 text-sm border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors">
<i class="fas fa-angle-double-right"></i>
</a>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<script>
// Multi-select functionality
function toggleSelectAll(checkbox) {
// Only select checkboxes that are currently visible
const desktopCheckboxes = document.querySelectorAll('.domain-checkbox');
const mobileCheckboxes = document.querySelectorAll('.domain-checkbox-mobile');
// Check if desktop view is visible (lg:block class)
const desktopTable = document.querySelector('.hidden.lg\\:block');
const isDesktopVisible = desktopTable && !desktopTable.classList.contains('hidden');
if (isDesktopVisible) {
// Desktop view is visible, select desktop checkboxes
desktopCheckboxes.forEach(cb => cb.checked = checkbox.checked);
} else {
// Mobile view is visible, select mobile checkboxes
mobileCheckboxes.forEach(cb => cb.checked = checkbox.checked);
}
updateBulkActions();
}
function updateBulkActions() {
// Only count checkboxes that are currently visible
const desktopCheckboxes = document.querySelectorAll('.domain-checkbox:checked');
const mobileCheckboxes = document.querySelectorAll('.domain-checkbox-mobile:checked');
// Check if desktop view is visible
const desktopTable = document.querySelector('.hidden.lg\\:block');
const isDesktopVisible = desktopTable && !desktopTable.classList.contains('hidden');
const checkboxes = isDesktopVisible ? desktopCheckboxes : mobileCheckboxes;
const bulkActions = document.getElementById('bulk-actions');
const selectedCount = document.getElementById('selected-count');
if (checkboxes.length > 0) {
bulkActions.classList.remove('hidden');
bulkActions.classList.add('flex');
selectedCount.textContent = `${checkboxes.length} selected`;
} else {
bulkActions.classList.add('hidden');
bulkActions.classList.remove('flex');
}
}
function clearSelection() {
const desktopCheckboxes = document.querySelectorAll('.domain-checkbox');
const mobileCheckboxes = document.querySelectorAll('.domain-checkbox-mobile');
// Check if desktop view is visible
const desktopTable = document.querySelector('.hidden.lg\\:block');
const isDesktopVisible = desktopTable && !desktopTable.classList.contains('hidden');
if (isDesktopVisible) {
desktopCheckboxes.forEach(cb => cb.checked = false);
} else {
mobileCheckboxes.forEach(cb => cb.checked = false);
}
document.getElementById('select-all').checked = false;
updateBulkActions();
}
function getSelectedIds() {
// Only get IDs from currently visible checkboxes
const desktopCheckboxes = document.querySelectorAll('.domain-checkbox:checked');
const mobileCheckboxes = document.querySelectorAll('.domain-checkbox-mobile:checked');
// Check if desktop view is visible
const desktopTable = document.querySelector('.hidden.lg\\:block');
const isDesktopVisible = desktopTable && !desktopTable.classList.contains('hidden');
const checkboxes = isDesktopVisible ? desktopCheckboxes : mobileCheckboxes;
return Array.from(checkboxes).map(cb => cb.value);
}
function bulkRefresh() {
const ids = getSelectedIds();
if (ids.length === 0) return;
const form = document.createElement('form');
form.method = 'POST';
form.action = '/domains/bulk-refresh';
ids.forEach(id => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'domain_ids[]';
input.value = id;
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
}
function bulkDelete() {
const ids = getSelectedIds();
if (ids.length === 0) return;
if (!confirm(`Delete ${ids.length} domain(s)? This action cannot be undone.`)) {
return;
}
const form = document.createElement('form');
form.method = 'POST';
form.action = '/domains/bulk-delete';
ids.forEach(id => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'domain_ids[]';
input.value = id;
form.appendChild(input);
});
document.body.appendChild(form);
form.submit();
}
function toggleAssignGroupDropdown() {
const dropdown = document.getElementById('assign-group-dropdown');
dropdown.classList.toggle('hidden');
}
// Update bulk assign form with selected IDs
document.getElementById('bulk-assign-form')?.addEventListener('submit', function(e) {
const ids = getSelectedIds();
const container = this;
// Clear existing hidden inputs
container.querySelectorAll('input[name="domain_ids[]"]').forEach(el => el.remove());
// Add selected IDs
ids.forEach(id => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = 'domain_ids[]';
input.value = id;
container.appendChild(input);
});
});
// Listen to checkbox changes
document.querySelectorAll('.domain-checkbox, .domain-checkbox-mobile').forEach(checkbox => {
checkbox.addEventListener('change', function() {
// Update the select-all checkbox state
const desktopCheckboxes = document.querySelectorAll('.domain-checkbox');
const mobileCheckboxes = document.querySelectorAll('.domain-checkbox-mobile');
// Check if desktop view is visible
const desktopTable = document.querySelector('.hidden.lg\\:block');
const isDesktopVisible = desktopTable && !desktopTable.classList.contains('hidden');
const checkboxes = isDesktopVisible ? desktopCheckboxes : mobileCheckboxes;
const checkedBoxes = isDesktopVisible ?
document.querySelectorAll('.domain-checkbox:checked') :
document.querySelectorAll('.domain-checkbox-mobile:checked');
const selectAllCheckbox = document.getElementById('select-all');
if (checkedBoxes.length === 0) {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = false;
} else if (checkedBoxes.length === checkboxes.length) {
selectAllCheckbox.checked = true;
selectAllCheckbox.indeterminate = false;
} else {
selectAllCheckbox.checked = false;
selectAllCheckbox.indeterminate = true;
}
updateBulkActions();
});
});
// Close dropdown when clicking outside
document.addEventListener('click', function(event) {
const dropdown = document.getElementById('assign-group-dropdown');
const button = event.target.closest('button[onclick="toggleAssignGroupDropdown()"]');
if (!button && !dropdown.contains(event.target)) {
dropdown?.classList.add('hidden');
}
});
// Handle window resize to sync checkboxes when switching between desktop/mobile views
window.addEventListener('resize', function() {
// Small delay to allow CSS classes to update
setTimeout(updateBulkActions, 100);
});
</script>
<?php
$content = ob_get_clean();
include __DIR__ . '/../layout/base.php';
?>

461
app/Views/domains/view.php Normal file
View File

@@ -0,0 +1,461 @@
<?php
$title = 'Domain Details';
$pageTitle = htmlspecialchars($domain['domain_name']);
$pageDescription = 'Domain information and monitoring status';
$pageIcon = 'fas fa-globe';
$whoisData = json_decode($domain['whois_data'] ?? '{}', true);
$daysLeft = !empty($domain['expiration_date']) ? floor((strtotime($domain['expiration_date']) - time()) / 86400) : null;
// Recalculate domain status if it's empty or error (for backward compatibility)
$domainStatus = $domain['status'];
if (empty($domainStatus) || $domainStatus === 'error') {
// Check WHOIS data for AVAILABLE status
$statusArray = $whoisData['status'] ?? [];
$isAvailable = false;
foreach ($statusArray as $status) {
if (stripos($status, 'AVAILABLE') !== false || stripos($status, 'FREE') !== false) {
$isAvailable = true;
break;
}
}
if ($isAvailable) {
$domainStatus = 'available';
} elseif ($daysLeft !== null) {
if ($daysLeft < 0) {
$domainStatus = 'expired';
} elseif ($daysLeft <= 30) {
$domainStatus = 'expiring_soon';
} else {
$domainStatus = 'active';
}
} else {
$domainStatus = 'error';
}
}
// Determine expiry color
$expiryColor = 'green';
if ($daysLeft !== null) {
if ($daysLeft < 0) $expiryColor = 'red';
elseif ($daysLeft <= 30) $expiryColor = 'orange';
elseif ($daysLeft <= 90) $expiryColor = 'yellow';
}
ob_start();
?>
<!-- Top Action Bar -->
<div class="mb-3 flex flex-wrap gap-2 justify-between items-center">
<div class="flex gap-2">
<?php
// Determine domain status badge
if ($domainStatus === 'available') {
$statusClass = 'bg-blue-100 text-blue-700 border-blue-200';
$statusText = 'Available (Not Registered)';
$statusIcon = 'fa-info-circle';
} elseif ($domainStatus === 'expired') {
$statusClass = 'bg-red-100 text-red-700 border-red-200';
$statusText = 'Expired';
$statusIcon = 'fa-times-circle';
} elseif ($domainStatus === 'expiring_soon' || ($daysLeft !== null && $daysLeft <= 30 && $daysLeft >= 0)) {
$statusClass = 'bg-orange-100 text-orange-700 border-orange-200';
$statusText = 'Expiring Soon';
$statusIcon = 'fa-exclamation-triangle';
} elseif ($domainStatus === 'active') {
$statusClass = 'bg-green-100 text-green-700 border-green-200';
$statusText = 'Active';
$statusIcon = 'fa-check-circle';
} elseif ($domainStatus === 'error') {
$statusClass = 'bg-gray-100 text-gray-700 border-gray-200';
$statusText = 'Error';
$statusIcon = 'fa-exclamation-circle';
} else {
$statusClass = 'bg-gray-100 text-gray-700 border-gray-200';
$statusText = ucfirst($domainStatus);
$statusIcon = 'fa-question-circle';
}
?>
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold <?= $statusClass ?>">
<i class="fas <?= $statusIcon ?> mr-1.5"></i>
<?= $statusText ?>
</span>
<?php if ($domainStatus !== 'available'): ?>
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold bg-<?= $expiryColor ?>-100 text-<?= $expiryColor ?>-800 border border-<?= $expiryColor ?>-200">
<i class="fas fa-calendar-alt mr-1.5"></i>
<?= $daysLeft !== null ? $daysLeft . ' days left' : 'No expiry date' ?>
</span>
<?php endif; ?>
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold bg-purple-100 text-purple-800 border border-purple-200">
<i class="fas fa-<?= $domain['is_active'] ? 'check-circle' : 'pause-circle' ?> mr-1.5"></i>
<?= $domain['is_active'] ? 'Monitoring Active' : 'Monitoring Paused' ?>
</span>
</div>
<div class="flex gap-2 items-center">
<form method="POST" action="/domains/<?= $domain['id'] ?>/refresh" class="inline">
<button type="submit" class="inline-flex items-center justify-center px-3 py-2 bg-green-600 text-white text-xs rounded-lg hover:bg-green-700 transition-colors font-medium min-w-[80px] h-[32px]">
<i class="fas fa-sync-alt mr-1.5"></i>
Refresh
</button>
</form>
<a href="/domains/<?= $domain['id'] ?>/edit" class="inline-flex items-center justify-center px-3 py-2 bg-blue-600 text-white text-xs rounded-lg hover:bg-blue-700 transition-colors font-medium min-w-[80px] h-[32px]">
<i class="fas fa-edit mr-1.5"></i>
Edit
</a>
<form method="POST" action="/domains/<?= $domain['id'] ?>/delete" onsubmit="return confirm('Delete this domain?')" class="inline">
<button type="submit" class="inline-flex items-center justify-center px-3 py-2 bg-red-600 text-white text-xs rounded-lg hover:bg-red-700 transition-colors font-medium min-w-[80px] h-[32px]">
<i class="fas fa-trash mr-1.5"></i>
Delete
</button>
</form>
<a href="/domains" class="inline-flex items-center justify-center px-3 py-2 border border-gray-300 text-gray-700 text-xs rounded-lg hover:bg-gray-50 transition-colors font-medium min-w-[80px] h-[32px]">
<i class="fas fa-arrow-left mr-1.5"></i>
Back
</a>
</div>
</div>
<!-- Main 2-Column Layout -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-3">
<!-- LEFT COLUMN -->
<div class="space-y-3">
<!-- Registration Details -->
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50">
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center">
<i class="fas fa-building text-gray-400 mr-2" style="font-size: 10px;"></i>
Registration Details
</h3>
</div>
<div class="p-4">
<div class="grid grid-cols-2 gap-x-4 gap-y-3 text-xs">
<div>
<label class="text-gray-500 font-medium block mb-0.5">Registrar</label>
<p class="text-gray-900 font-semibold"><?= htmlspecialchars($domain['registrar'] ?? 'Unknown') ?></p>
</div>
<?php if (!empty($domain['registrar_url'])): ?>
<div>
<label class="text-gray-500 font-medium block mb-0.5">Registrar URL</label>
<a href="<?= htmlspecialchars($domain['registrar_url']) ?>" target="_blank" class="text-blue-600 hover:text-blue-800 flex items-center">
<i class="fas fa-external-link-alt mr-1" style="font-size: 9px;"></i>
Visit
</a>
</div>
<?php endif; ?>
<?php if (!empty($domain['abuse_email'])): ?>
<div>
<label class="text-gray-500 font-medium block mb-0.5">Abuse Contact</label>
<a href="mailto:<?= htmlspecialchars($domain['abuse_email']) ?>" class="text-blue-600 hover:text-blue-800">
<?= htmlspecialchars($domain['abuse_email']) ?>
</a>
</div>
<?php endif; ?>
<?php if (isset($whoisData['whois_server'])): ?>
<div>
<label class="text-gray-500 font-medium block mb-0.5">WHOIS Server</label>
<p class="text-gray-900 font-mono"><?= htmlspecialchars($whoisData['whois_server']) ?></p>
</div>
<?php endif; ?>
<?php if (isset($whoisData['owner'])): ?>
<div class="col-span-2">
<label class="text-gray-500 font-medium block mb-0.5">Owner</label>
<p class="text-gray-900"><?= htmlspecialchars($whoisData['owner']) ?></p>
</div>
<?php endif; ?>
</div>
</div>
</div>
<!-- Important Dates -->
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50">
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center">
<i class="fas fa-calendar text-gray-400 mr-2" style="font-size: 10px;"></i>
Important Dates
</h3>
</div>
<div class="p-4">
<div class="space-y-2">
<?php if (!empty($domain['expiration_date'])): ?>
<div class="flex items-center justify-between p-2 bg-<?= $expiryColor ?>-50 rounded border border-<?= $expiryColor ?>-200">
<div class="flex items-center">
<div class="w-7 h-7 bg-<?= $expiryColor ?>-500 rounded flex items-center justify-center mr-2">
<i class="fas fa-exclamation-triangle text-white text-xs"></i>
</div>
<div>
<p class="text-xs text-gray-600 font-medium">Expiration</p>
<p class="text-xs font-semibold text-gray-900"><?= date('M j, Y', strtotime($domain['expiration_date'])) ?></p>
</div>
</div>
<span class="px-2 py-1 bg-<?= $expiryColor ?>-100 text-<?= $expiryColor ?>-800 rounded text-xs font-bold">
<?= $daysLeft ?> days
</span>
</div>
<?php endif; ?>
<?php if (!empty($domain['updated_date'])): ?>
<div class="flex items-center p-2 bg-blue-50 rounded border border-blue-200">
<div class="w-7 h-7 bg-blue-500 rounded flex items-center justify-center mr-2">
<i class="fas fa-clock text-white text-xs"></i>
</div>
<div>
<p class="text-xs text-gray-600 font-medium">Last Updated</p>
<p class="text-xs font-semibold text-gray-900"><?= date('M j, Y', strtotime($domain['updated_date'])) ?></p>
</div>
</div>
<?php endif; ?>
<?php if (isset($whoisData['creation_date'])): ?>
<div class="flex items-center p-2 bg-green-50 rounded border border-green-200">
<div class="w-7 h-7 bg-green-500 rounded flex items-center justify-center mr-2">
<i class="fas fa-calendar-plus text-white text-xs"></i>
</div>
<div>
<p class="text-xs text-gray-600 font-medium">Created</p>
<p class="text-xs font-semibold text-gray-900"><?= date('M j, Y', strtotime($whoisData['creation_date'])) ?></p>
</div>
</div>
<?php endif; ?>
<div class="flex items-center p-2 bg-purple-50 rounded border border-purple-200">
<div class="w-7 h-7 bg-purple-500 rounded flex items-center justify-center mr-2">
<i class="fas fa-sync text-white text-xs"></i>
</div>
<div>
<p class="text-xs text-gray-600 font-medium">Last Checked</p>
<p class="text-xs font-semibold text-gray-900"><?= date('M j, Y H:i', strtotime($domain['last_checked'])) ?></p>
</div>
</div>
</div>
</div>
</div>
<!-- Nameservers -->
<?php if (!empty($whoisData['nameservers'])): ?>
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50">
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center">
<i class="fas fa-server text-gray-400 mr-2" style="font-size: 10px;"></i>
Nameservers (<?= count($whoisData['nameservers']) ?>)
</h3>
</div>
<div class="p-4">
<div class="space-y-1.5">
<?php foreach ($whoisData['nameservers'] as $index => $ns): ?>
<div class="flex items-center p-2 bg-gray-50 rounded hover:bg-gray-100 transition-colors">
<div class="w-6 h-6 bg-teal-500 rounded flex items-center justify-center text-white font-bold text-xs mr-2">
<?= $index + 1 ?>
</div>
<p class="font-mono text-xs text-gray-800"><?= htmlspecialchars($ns) ?></p>
</div>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
<!-- Domain Status -->
<?php if (!empty($whoisData['status']) && is_array($whoisData['status'])): ?>
<?php
// Pre-filter to count only valid statuses
$validStatuses = [];
foreach ($whoisData['status'] as $status) {
$cleanStatus = trim($status);
// Skip if it's just a URL or starts with http/https or //
if (empty($cleanStatus) ||
strpos($cleanStatus, 'http') === 0 ||
strpos($cleanStatus, '//') === 0 ||
strpos($cleanStatus, 'www.') === 0) {
continue;
}
// Keep the full status text, don't split by spaces
// Skip if after cleaning it's empty or just a URL
if (empty($cleanStatus) || strpos($cleanStatus, 'http') === 0 || strpos($cleanStatus, '//') === 0) {
continue;
}
$validStatuses[] = $cleanStatus;
}
?>
<?php if (!empty($validStatuses)): ?>
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50">
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center">
<i class="fas fa-info-circle text-gray-400 mr-2" style="font-size: 10px;"></i>
Domain Status (<?= count($validStatuses) ?>)
</h3>
</div>
<div class="p-4">
<div class="flex flex-wrap gap-1.5">
<?php foreach ($validStatuses as $cleanStatus): ?>
<?php
// Convert to readable format
$readableStatus = $cleanStatus;
// Convert camelCase to readable format (for cases like "clientTransferProhibited")
$readableStatus = preg_replace('/([a-z])([A-Z])/', '$1 $2', $readableStatus);
// Convert underscores to spaces and capitalize words
$readableStatus = str_replace('_', ' ', $readableStatus);
$readableStatus = ucwords(strtolower($readableStatus));
?>
<span class="px-2 py-1 bg-blue-100 text-blue-800 rounded text-xs font-medium" title="<?= htmlspecialchars($cleanStatus) ?>">
<?= htmlspecialchars($readableStatus) ?>
</span>
<?php endforeach; ?>
</div>
</div>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
<!-- RIGHT COLUMN -->
<div class="space-y-3">
<!-- Notification Group -->
<?php if (!empty($domain['group_name'])): ?>
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50">
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center">
<i class="fas fa-bell text-gray-400 mr-2" style="font-size: 10px;"></i>
Notification Group
</h3>
</div>
<div class="p-4">
<div class="flex items-center mb-3">
<div class="w-10 h-10 bg-green-100 rounded-lg flex items-center justify-center mr-3">
<i class="fas fa-users text-green-600"></i>
</div>
<div>
<p class="font-semibold text-sm text-gray-900"><?= htmlspecialchars($domain['group_name']) ?></p>
<?php if (!empty($domain['channels'])): ?>
<?php
$activeChannels = array_filter($domain['channels'], fn($ch) => $ch['is_active']);
?>
<p class="text-xs text-gray-600">
<?= count($activeChannels) ?> / <?= count($domain['channels']) ?> channels active
</p>
<?php endif; ?>
</div>
</div>
<?php if (!empty($domain['channels'])): ?>
<div class="grid grid-cols-2 gap-2">
<?php foreach ($domain['channels'] as $channel): ?>
<div class="flex items-center p-2 rounded <?= $channel['is_active'] ? 'bg-green-50 border border-green-200' : 'bg-gray-50 border border-gray-200' ?>">
<i class="fas fa-<?= $channel['is_active'] ? 'check-circle text-green-600' : 'times-circle text-gray-400' ?> mr-2 text-xs"></i>
<span class="text-xs font-medium text-gray-700"><?= ucfirst($channel['channel_type']) ?></span>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<?php else: ?>
<div class="bg-orange-50 rounded-lg border border-orange-200 p-4">
<div class="flex items-start mb-2">
<i class="fas fa-exclamation-triangle text-orange-500 mr-2 mt-0.5"></i>
<div>
<h3 class="text-xs font-semibold text-gray-900">No Group Assigned</h3>
<p class="text-xs text-gray-600 mt-0.5">Won't receive notifications</p>
</div>
</div>
<a href="/domains/<?= $domain['id'] ?>/edit" class="block w-full text-center px-3 py-1.5 bg-orange-500 text-white text-xs rounded-lg hover:bg-orange-600 transition-colors font-medium">
<i class="fas fa-plus mr-1"></i>
Assign Group
</a>
</div>
<?php endif; ?>
<!-- Notification History -->
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 bg-gray-50">
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center">
<i class="fas fa-history text-gray-400 mr-2" style="font-size: 10px;"></i>
Notification History (<?= count($logs) ?>)
</h3>
</div>
<div class="overflow-hidden">
<?php if (empty($logs)): ?>
<div class="p-8 text-center">
<i class="fas fa-bell-slash text-gray-300 text-3xl mb-2"></i>
<p class="text-xs text-gray-500">No notifications sent yet</p>
</div>
<?php else: ?>
<div class="max-h-96 overflow-y-auto">
<table class="min-w-full divide-y divide-gray-200 text-xs">
<thead class="bg-gray-50 sticky top-0">
<tr>
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 uppercase">Channel</th>
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 uppercase">Status</th>
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 uppercase">Date</th>
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 uppercase">Message</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
<?php foreach ($logs as $log): ?>
<tr class="hover:bg-gray-50">
<td class="px-3 py-2 whitespace-nowrap">
<span class="px-2 py-0.5 rounded text-xs font-medium bg-blue-100 text-blue-800">
<?= ucfirst($log['channel_type']) ?>
</span>
</td>
<td class="px-3 py-2 whitespace-nowrap">
<?php $statusClass = $log['status'] === 'sent' ? 'bg-green-100 text-green-800' : 'bg-red-100 text-red-800'; ?>
<span class="px-2 py-0.5 rounded text-xs font-medium <?= $statusClass ?>">
<?= ucfirst($log['status']) ?>
</span>
</td>
<td class="px-3 py-2 whitespace-nowrap text-gray-600"><?= date('M j, H:i', strtotime($log['sent_at'])) ?></td>
<td class="px-3 py-2 text-gray-700 max-w-xs truncate" title="<?= htmlspecialchars($log['message']) ?>">
<?= htmlspecialchars($log['message']) ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<!-- Raw WHOIS Data (Collapsible) -->
<?php if (!empty($domain['whois_data'])): ?>
<div class="bg-white rounded-lg border border-gray-200 overflow-hidden">
<button onclick="toggleWhoisData()" class="w-full px-4 py-2 border-b border-gray-200 bg-gray-50 text-left hover:bg-gray-100 transition-colors">
<h3 class="text-xs font-semibold text-gray-700 uppercase tracking-wider flex items-center justify-between">
<span class="flex items-center">
<i class="fas fa-code text-gray-400 mr-2" style="font-size: 10px;"></i>
Raw WHOIS Data
</span>
<i class="fas fa-chevron-down text-gray-400 text-xs transition-transform" id="whois-chevron"></i>
</h3>
</button>
<div id="whois-data" class="hidden p-4 bg-gray-900 max-h-64 overflow-y-auto">
<pre class="text-xs text-green-400 font-mono"><?= htmlspecialchars(json_encode($whoisData, JSON_PRETTY_PRINT)) ?></pre>
</div>
</div>
<?php endif; ?>
</div>
</div>
<script>
function toggleWhoisData() {
const dataDiv = document.getElementById('whois-data');
const chevron = document.getElementById('whois-chevron');
dataDiv.classList.toggle('hidden');
chevron.classList.toggle('rotate-180');
}
</script>
<?php
$content = ob_get_clean();
include __DIR__ . '/../layout/base.php';
?>