Add DNS monitoring and refresh functionality

Introduce DNS monitoring: add DnsService (comprehensive DNS lookup, crt.sh discovery, Cloudflare detection, IP enrichment) and a new DnsRecord model to persist snapshots, manage diffs, and provide queries/stats. Update DomainController to support a dns_monitoring_enabled flag, refactor WHOIS/DNS refresh logic into performWhoisRefresh/performDnsRefresh, and add endpoints for refreshWhois, refreshDns and refreshAll; send notifications when DNS monitoring is toggled. Add UI templates/tabs for DNS, billing, notifications, overview, SSL and WHOIS and wire DNS data into the domain view; expose cached IP details. Add cron/check_dns.php and migration 027_add_dns_monitoring.sql (and include it in installer migration lists). Other tweaks: safer EmailHelper subject handling, TldRegistry search improvements, domain sorting using an effective status (expiring_soon), Discord channel null-safe fields, settings UI additions (domain_view_template and cron staleness warnings), and route/migration updates. This enables scheduled and manual DNS scans with persistent records and notifications.
This commit is contained in:
Hosteroid
2026-03-08 14:32:05 +02:00
parent db094d6d8b
commit 8559e903b9
29 changed files with 4493 additions and 100 deletions

View File

@@ -140,15 +140,25 @@
</div>
<!-- Active Monitoring -->
<div class="bg-gray-50 dark:bg-slate-900 rounded-lg p-4 border border-gray-200 dark:border-slate-700">
<label class="flex items-center cursor-pointer">
<div id="dns-monitoring" class="bg-gray-50 dark:bg-slate-900 rounded-lg p-4 border border-gray-200 dark:border-slate-700 space-y-4">
<label class="flex items-start cursor-pointer">
<input type="checkbox"
name="is_active"
{{ domain.is_active ? 'checked' : '' }}
class="w-4 h-4 text-primary border-gray-300 dark:border-slate-600 rounded focus:ring-primary cursor-pointer">
class="w-4 h-4 mt-0.5 text-primary border-gray-300 dark:border-slate-600 rounded focus:ring-primary cursor-pointer">
<div class="ml-3">
<span class="text-sm font-medium text-gray-900 dark:text-white">Enable Active Monitoring</span>
<p class="text-xs text-gray-600 dark:text-slate-400 mt-0.5">When enabled, this domain will be checked regularly and notifications will be sent</p>
<p class="text-xs text-gray-600 dark:text-slate-400 mt-1">When enabled, this domain will be checked regularly and notifications will be sent</p>
</div>
</label>
<label class="flex items-start cursor-pointer pt-2 border-t border-gray-200 dark:border-slate-700">
<input type="checkbox"
name="dns_monitoring_enabled"
{{ domain.dns_monitoring_enabled|default(1) ? 'checked' : '' }}
class="w-4 h-4 mt-0.5 text-primary border-gray-300 dark:border-slate-600 rounded focus:ring-primary cursor-pointer">
<div class="ml-3">
<span class="text-sm font-medium text-gray-900 dark:text-white">Enable DNS Monitoring</span>
<p class="text-xs text-gray-600 dark:text-slate-400 mt-1">When enabled, DNS records will be checked for changes and you'll receive alerts</p>
</div>
</label>
</div>
@@ -177,7 +187,7 @@
<i class="fas fa-eye text-blue-600 dark:text-blue-400 mr-2 text-sm"></i>
<span class="text-sm font-medium text-gray-700 dark:text-slate-300">View Details</span>
</a>
<form method="POST" action="/domains/{{ domain.id }}/refresh" class="m-0">
<form method="POST" action="/domains/{{ domain.id }}/refresh-whois" class="m-0">
{{ csrf_field() }}
<button type="submit"
class="w-full flex items-center justify-center p-3 bg-white dark:bg-slate-800 border border-gray-200 dark:border-slate-700 rounded-lg hover:border-green-300 dark:hover:border-green-700 hover:bg-green-50 dark:hover:bg-green-500/10 transition-colors group">

View File

@@ -369,7 +369,7 @@
<a href="/domains/{{ domain.id }}" class="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300" title="View">
<i class="fas fa-eye"></i>
</a>
<form method="POST" action="/domains/{{ domain.id }}/refresh" class="inline">
<form method="POST" action="/domains/{{ domain.id }}/refresh-whois" class="inline">
{{ csrf_field() }}
<button type="submit" class="text-green-600 hover:text-green-800 dark:text-green-400 dark:hover:text-green-300" title="Refresh WHOIS">
<i class="fas fa-sync-alt"></i>

View File

@@ -0,0 +1,279 @@
<!-- BILLING TAB CONTENT -->
<!-- Preview Banner -->
<div class="mb-3 bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-800 rounded-lg p-3">
<div class="flex items-center">
<i class="fas fa-flask text-amber-600 dark:text-amber-400 mr-2" style="font-size: 12px;"></i>
<div>
<p class="text-xs font-semibold text-amber-900 dark:text-amber-300">Preview</p>
<p class="text-xs text-amber-800 dark:text-amber-400 mt-0.5">Financial tracking is coming soon. This is a design preview with sample data and might change in the future.</p>
</div>
</div>
</div>
<!-- Purchase Source Info -->
<div class="bg-blue-50 dark:bg-blue-500/10 border border-blue-200 dark:border-blue-800 rounded-lg p-3 mb-3">
<div class="flex items-center justify-between">
<div class="flex items-center">
<i class="fas fa-shopping-cart text-blue-600 dark:text-blue-400 mr-2" style="font-size: 14px;"></i>
<div>
<p class="text-xs font-semibold text-blue-900 dark:text-blue-300">Purchased From</p>
<p class="text-xs text-blue-700 dark:text-blue-400 mt-0.5">Sedo Marketplace - $1,200.00 on Jan 15, 2020</p>
</div>
</div>
<a href="https://sedo.com/account" target="_blank" class="inline-flex items-center px-3 py-1.5 bg-blue-600 text-white text-xs rounded hover:bg-blue-700 transition-colors font-medium">
<i class="fas fa-external-link-alt mr-1" style="font-size: 9px;"></i>
Go to Seller
</a>
</div>
</div>
<!-- Financial Summary Cards -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-3 mb-3">
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-3">
<div class="flex items-center justify-between">
<div>
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase">Purchase Price</p>
<p class="text-lg font-semibold text-gray-900 dark:text-white mt-0.5">$1,200.00</p>
<p class="text-xs text-gray-500 dark:text-slate-400 mt-0.5">Jan 15, 2020</p>
</div>
<div class="w-8 h-8 bg-blue-50 dark:bg-blue-500/10 rounded flex items-center justify-center">
<i class="fas fa-shopping-cart text-blue-600 dark:text-blue-400" style="font-size: 14px;"></i>
</div>
</div>
</div>
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-3">
<div class="flex items-center justify-between">
<div>
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase">Total Renewals</p>
<p class="text-lg font-semibold text-orange-600 dark:text-orange-400 mt-0.5">$450.00</p>
<p class="text-xs text-gray-500 dark:text-slate-400 mt-0.5">3 payments</p>
</div>
<div class="w-8 h-8 bg-orange-50 dark:bg-orange-500/10 rounded flex items-center justify-center">
<i class="fas fa-redo text-orange-600 dark:text-orange-400" style="font-size: 14px;"></i>
</div>
</div>
</div>
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-3">
<div class="flex items-center justify-between">
<div>
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase">Total Invested</p>
<p class="text-lg font-semibold text-indigo-600 dark:text-indigo-400 mt-0.5">$1,700.00</p>
<p class="text-xs text-gray-500 dark:text-slate-400 mt-0.5">All expenses</p>
</div>
<div class="w-8 h-8 bg-indigo-50 dark:bg-indigo-500/10 rounded flex items-center justify-center">
<i class="fas fa-wallet text-indigo-600 dark:text-indigo-400" style="font-size: 14px;"></i>
</div>
</div>
</div>
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-3">
<div class="flex items-center justify-between">
<div>
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase">Profit/Loss</p>
<p class="text-lg font-semibold text-gray-400 dark:text-slate-500 mt-0.5">-</p>
<p class="text-xs text-gray-500 dark:text-slate-400 mt-0.5">Not sold</p>
</div>
<div class="w-8 h-8 bg-gray-50 dark:bg-slate-700 rounded flex items-center justify-center">
<i class="fas fa-chart-line text-gray-600 dark:text-slate-400" style="font-size: 14px;"></i>
</div>
</div>
</div>
</div>
<!-- Next Renewal Alert -->
<div class="bg-orange-50 dark:bg-orange-500/10 border border-orange-200 dark:border-orange-800 rounded-lg p-3 mb-3">
<div class="flex items-center justify-between">
<div class="flex items-center">
<i class="fas fa-calendar-alt text-orange-600 dark:text-orange-400 mr-2" style="font-size: 14px;"></i>
<div>
<p class="text-xs font-semibold text-orange-900 dark:text-orange-300">Next Renewal Due</p>
<p class="text-xs text-orange-700 dark:text-orange-400 mt-0.5">Jan 15, 2026 <span class="font-semibold">(65 days)</span> - Estimated: $150.00</p>
</div>
</div>
<button class="inline-flex items-center px-3 py-1.5 bg-orange-600 text-white text-xs rounded hover:bg-orange-700 transition-colors font-medium">
<i class="fas fa-plus mr-1" style="font-size: 9px;"></i>
Add Payment
</button>
</div>
</div>
<!-- Action Buttons -->
<div class="flex gap-2 mb-3">
<button class="inline-flex items-center px-3 py-2 bg-primary text-white text-xs rounded-lg hover:bg-primary-dark transition-colors font-medium"><i class="fas fa-plus mr-1.5" style="font-size: 10px;"></i>Add Transaction</button>
<button class="inline-flex items-center px-3 py-2 bg-green-600 text-white text-xs rounded-lg hover:bg-green-700 transition-colors font-medium"><i class="fas fa-hand-holding-usd mr-1.5" style="font-size: 10px;"></i>Record Sale</button>
<button class="inline-flex items-center px-3 py-2 bg-indigo-600 text-white text-xs rounded-lg hover:bg-indigo-700 transition-colors font-medium"><i class="fas fa-file-invoice-dollar mr-1.5" style="font-size: 10px;"></i>Export Report</button>
</div>
<!-- Transaction History (static sample) -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
<i class="fas fa-history text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Transaction History
<span class="ml-2 px-1.5 py-0.5 bg-primary text-white text-xs font-semibold rounded">5</span>
</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full">
<thead class="bg-gray-50 dark:bg-slate-900">
<tr>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Date</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Type</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Amount</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Company/Seller</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Invoice</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Payment Method</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Status</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Notes</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-slate-700">
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700">
<td class="px-4 py-3 text-xs text-gray-900 dark:text-white whitespace-nowrap">Jan 15, 2020</td>
<td class="px-4 py-3 whitespace-nowrap"><span class="inline-flex items-center px-2 py-0.5 bg-blue-100 dark:bg-blue-500/10 text-blue-800 dark:text-blue-400 text-xs font-semibold rounded"><i class="fas fa-shopping-cart mr-1" style="font-size: 9px;"></i>Purchase</span></td>
<td class="px-4 py-3 text-xs font-semibold text-gray-900 dark:text-white whitespace-nowrap">$1,200.00</td>
<td class="px-4 py-3 text-xs text-gray-900 dark:text-white whitespace-nowrap">Sedo Marketplace</td>
<td class="px-4 py-3 text-xs font-mono text-blue-600 dark:text-blue-400 whitespace-nowrap">SEDO-2020-001234</td>
<td class="px-4 py-3 text-xs text-gray-600 dark:text-slate-400 whitespace-nowrap">Credit Card</td>
<td class="px-4 py-3 whitespace-nowrap"><span class="inline-flex items-center px-2 py-0.5 bg-green-100 dark:bg-green-500/10 text-green-800 dark:text-green-400 text-xs font-semibold rounded"><i class="fas fa-check-circle mr-1" style="font-size: 8px;"></i>Paid</span></td>
<td class="px-4 py-3 text-xs text-gray-600 dark:text-slate-400">Initial domain purchase from Sedo marketplace</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- Financial Charts -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-3 mt-3">
<!-- Expense Breakdown (Donut Chart) -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
<i class="fas fa-chart-pie text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Expense Breakdown
</h3>
</div>
<div class="p-4">
<canvas id="expenseChart" height="200"></canvas>
</div>
</div>
<!-- Expense Timeline (Line Chart) -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
<i class="fas fa-chart-line text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Expense Timeline
</h3>
</div>
<div class="p-4">
<canvas id="timelineChart" height="200"></canvas>
</div>
</div>
</div>
<!-- Chart.js CDN -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<script>
// Expense Breakdown Donut Chart
const expenseCtx = document.getElementById('expenseChart')?.getContext('2d');
if (expenseCtx) {
new Chart(expenseCtx, {
type: 'doughnut',
data: {
labels: ['Initial Purchase', 'Renewals', 'Transfers', 'Other'],
datasets: [{
data: [1200, 450, 50, 0],
backgroundColor: [
'rgba(59, 130, 246, 0.8)',
'rgba(251, 146, 60, 0.8)',
'rgba(168, 85, 247, 0.8)',
'rgba(156, 163, 175, 0.8)'
],
borderColor: [
'rgb(59, 130, 246)',
'rgb(251, 146, 60)',
'rgb(168, 85, 247)',
'rgb(156, 163, 175)'
],
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
padding: 15,
font: { size: 11 },
generateLabels: function(chart) {
const data = chart.data;
if (data.labels.length && data.datasets.length) {
return data.labels.map((label, i) => {
const value = data.datasets[0].data[i];
return { text: `${label}: $${value.toFixed(2)}`, fillStyle: data.datasets[0].backgroundColor[i], hidden: false, index: i };
});
}
return [];
}
}
},
tooltip: {
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.parsed || 0;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
return `${label}: $${value.toFixed(2)} (${percentage}%)`;
}
}
}
}
}
});
}
// Expense Timeline Chart
const timelineCtx = document.getElementById('timelineChart')?.getContext('2d');
if (timelineCtx) {
new Chart(timelineCtx, {
type: 'line',
data: {
labels: ['2020', '2021', '2022', '2023', '2024'],
datasets: [{
label: 'Cumulative Investment',
data: [1200, 1350, 1500, 1650, 1700],
borderColor: 'rgb(99, 102, 241)',
backgroundColor: 'rgba(99, 102, 241, 0.1)',
tension: 0.3,
fill: true,
pointRadius: 4,
pointHoverRadius: 6,
pointBackgroundColor: 'rgb(99, 102, 241)',
pointBorderColor: '#fff',
pointBorderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: { legend: { display: false }, tooltip: { callbacks: { label: function(context) { return `Total Invested: $${context.parsed.y.toFixed(2)}`; } } } },
scales: {
y: {
beginAtZero: true,
ticks: { callback: function(value) { return '$' + value; }, font: { size: 10 } },
grid: { color: 'rgba(0, 0, 0, 0.05)' }
},
x: {
ticks: { font: { size: 10 } },
grid: { display: false }
}
}
}
});
}
</script>

View File

@@ -0,0 +1,507 @@
{# DNS TAB CONTENT #}
{% set totalDnsRecords = dnsRecordCount|default(0) %}
{% set dnsMonitoringEnabled = domain.dns_monitoring_enabled|default(1) %}
{% if not dnsMonitoringEnabled %}
<!-- DNS Monitoring Disabled - show only message, no records -->
<div class="mb-4 bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-500/30 rounded-lg p-4">
<div class="flex items-start">
<i class="fas fa-pause-circle text-amber-500 dark:text-amber-400 mt-0.5 mr-3" style="font-size: 18px;"></i>
<div>
<h3 class="text-sm font-semibold text-amber-800 dark:text-amber-300">DNS monitoring is disabled</h3>
<p class="text-xs text-amber-700 dark:text-amber-400 mt-1">This domain is not checked by the DNS cron. Enable it in Edit to track DNS changes.</p>
<a href="/domains/{{ domain.id }}/edit#dns-monitoring" class="inline-flex items-center mt-2 text-xs font-medium text-amber-700 dark:text-amber-300 hover:text-amber-900 dark:hover:text-amber-100">
<i class="fas fa-edit mr-1"></i>Enable DNS monitoring in Edit
</a>
</div>
</div>
</div>
{% else %}
{% if totalDnsRecords == 0 %}
<!-- No DNS Data Yet -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-8 text-center">
<i class="fas fa-network-wired text-gray-300 dark:text-slate-600 mb-3" style="font-size: 36px;"></i>
<h3 class="text-sm font-semibold text-gray-900 dark:text-white mb-1">No DNS Records Yet</h3>
<p class="text-xs text-gray-500 dark:text-slate-400 mb-4">Click "Refresh DNS" to fetch the current DNS records for this domain.</p>
{% if domain %}
<form method="POST" action="/domains/{{ domain.id }}/refresh-dns" class="inline" onsubmit="return handleDnsRefresh(this)">
{{ csrf_field()|raw }}
<button type="submit" class="dns-refresh-btn inline-flex items-center px-4 py-2 bg-green-600 text-white text-xs rounded-lg hover:bg-green-700 transition-colors font-medium">
<i class="fas fa-sync-alt mr-1.5" style="font-size: 10px;"></i>
<span class="btn-label">Refresh DNS</span>
</button>
</form>
{% endif %}
</div>
{% else %}
<!-- Action Bar -->
<div class="flex justify-between items-center mb-3">
<div class="flex items-center gap-3">
<p class="text-xs text-gray-600 dark:text-slate-400">
<i class="far fa-clock mr-1"></i>
Last checked: {{ domain.dns_last_checked ? domain.dns_last_checked|date('M d, Y H:i') : 'Never' }}
</p>
{% if dnsHasCloudflare|default(false) %}
<span class="inline-flex items-center px-2 py-1 bg-orange-100 dark:bg-orange-500/10 text-orange-800 dark:text-orange-400 text-xs font-semibold rounded border border-orange-200 dark:border-orange-800">
<i class="fas fa-cloud mr-1" style="font-size: 10px;"></i>
Cloudflare Detected
</span>
{% endif %}
</div>
{% if domain and dnsMonitoringEnabled %}
<form method="POST" action="/domains/{{ domain.id }}/refresh-dns" class="inline" onsubmit="return handleDnsRefresh(this)">
{{ csrf_field()|raw }}
<button type="submit" class="dns-refresh-btn inline-flex items-center px-3 py-2 bg-green-600 text-white text-xs rounded-lg hover:bg-green-700 transition-colors font-medium">
<i class="fas fa-sync-alt mr-1.5" style="font-size: 10px;"></i>
<span class="btn-label">Refresh DNS</span>
</button>
</form>
{% endif %}
</div>
<!-- DNS Records by Type -->
<div class="space-y-3">
{# ===== SOA Record (Start of Authority) — shown first ===== #}
{% if dnsRecords['SOA'] is defined and dnsRecords['SOA']|length > 0 %}
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 bg-gray-100 dark:bg-slate-700 border-b border-gray-200 dark:border-slate-600">
<h3 class="text-xs font-semibold text-gray-900 dark:text-white flex items-center">
<i class="fas fa-info-circle text-gray-500 dark:text-slate-400 mr-2" style="font-size: 10px;"></i>
SOA Record (Start of Authority)
</h3>
</div>
{% for record in dnsRecords['SOA'] %}
{% set rawData = record.raw_data ? record.raw_data|from_json : null %}
<div class="p-4">
<div class="grid grid-cols-2 md:grid-cols-4 gap-3">
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Primary NS</label>
<p class="text-xs font-mono text-gray-900 dark:text-white mt-1">{{ record.value }}</p>
</div>
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Admin</label>
<p class="text-xs font-mono text-gray-900 dark:text-white mt-1">{{ rawData.rname|default('N/A') }}</p>
</div>
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Serial</label>
<p class="text-xs font-mono text-gray-900 dark:text-white mt-1">{{ rawData.serial|default('N/A') }}</p>
</div>
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">TTL</label>
<p class="text-xs font-mono text-gray-900 dark:text-white mt-1">{{ record.ttl }}s</p>
</div>
</div>
<div class="grid grid-cols-2 md:grid-cols-4 gap-3 mt-3 pt-3 border-t border-gray-100 dark:border-slate-700">
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Refresh</label>
<p class="text-xs font-mono text-gray-900 dark:text-white mt-1">{{ rawData.refresh|default('N/A') }}s</p>
</div>
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Retry</label>
<p class="text-xs font-mono text-gray-900 dark:text-white mt-1">{{ rawData.retry|default('N/A') }}s</p>
</div>
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Expire</label>
<p class="text-xs font-mono text-gray-900 dark:text-white mt-1">{{ rawData.expire|default('N/A') }}s</p>
</div>
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Min TTL</label>
<p class="text-xs font-mono text-gray-900 dark:text-white mt-1">{{ rawData['minimum-ttl']|default('N/A') }}s</p>
</div>
</div>
</div>
{% endfor %}
</div>
{% endif %}
{# ===== A Records ===== #}
{% if dnsRecords['A'] is defined and dnsRecords['A']|length > 0 %}
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 bg-blue-50 dark:bg-blue-500/10 border-b border-blue-200 dark:border-blue-800">
<h3 class="text-xs font-semibold text-gray-900 dark:text-white flex items-center">
<i class="fas fa-circle text-blue-600 dark:text-blue-400 mr-2" style="font-size: 8px;"></i>
A Records (IPv4)
<span class="ml-2 px-1.5 py-0.5 bg-blue-600 text-white text-xs font-semibold rounded">{{ dnsRecords['A']|length }}</span>
</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full">
<thead class="bg-gray-50 dark:bg-slate-900">
<tr>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Host</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">IP Address</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">PTR</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">ASN</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">TTL</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-slate-700">
{% for record in dnsRecords['A'] %}
{% set ipInfo = dnsIpDetails[record.value]|default(null) %}
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700">
<td class="px-4 py-2 text-xs font-medium text-gray-900 dark:text-white">
{% if record.host == '@' %}
<span class="text-blue-600 dark:text-blue-400">@ (root)</span>
{% else %}
{{ record.host }}
{% endif %}
</td>
<td class="px-4 py-2 text-xs">
<span class="font-mono text-gray-900 dark:text-white">{{ record.value }}</span>
{% if record.is_cloudflare %}
<i class="fas fa-cloud text-orange-500 dark:text-orange-400 ml-1.5" style="font-size: 10px;" title="Cloudflare"></i>
{% endif %}
</td>
<td class="px-4 py-2 text-xs font-mono text-gray-600 dark:text-slate-400 max-w-[200px] truncate" title="{{ ipInfo.reverse|default('') }}">
{{ ipInfo.reverse|default('-') }}
</td>
<td class="px-4 py-2.5">
{% if ipInfo and ipInfo.as %}
<div class="flex items-center gap-2">
{% if ipInfo.countryCode %}
<span class="fi fi-{{ ipInfo.countryCode|lower }}" style="font-size: 16px;"></span>
{% endif %}
<div class="text-xs">
<div class="font-semibold text-gray-900 dark:text-white">{{ ipInfo.as|split(' ')|first }}</div>
<div class="text-gray-600 dark:text-slate-400">{{ ipInfo.org|default(ipInfo.isp|default('')) }}</div>
{% if ipInfo.city or ipInfo.regionName %}
<div class="text-gray-500 dark:text-slate-500">{{ ipInfo.city|default('') }}{% if ipInfo.city and ipInfo.regionName %}, {% endif %}{{ ipInfo.regionName|default('') }}</div>
{% endif %}
</div>
</div>
{% else %}
<span class="text-gray-400 dark:text-slate-500 text-xs">-</span>
{% endif %}
</td>
<td class="px-4 py-2 text-xs text-gray-600 dark:text-slate-400">{{ record.ttl }}s</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{# ===== AAAA Records ===== #}
{% if dnsRecords['AAAA'] is defined and dnsRecords['AAAA']|length > 0 %}
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 bg-indigo-50 dark:bg-indigo-500/10 border-b border-indigo-200 dark:border-indigo-800">
<h3 class="text-xs font-semibold text-gray-900 dark:text-white flex items-center">
<i class="fas fa-circle text-indigo-600 dark:text-indigo-400 mr-2" style="font-size: 8px;"></i>
AAAA Records (IPv6)
<span class="ml-2 px-1.5 py-0.5 bg-indigo-600 text-white text-xs font-semibold rounded">{{ dnsRecords['AAAA']|length }}</span>
</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full">
<thead class="bg-gray-50 dark:bg-slate-900">
<tr>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Host</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">IPv6 Address</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">PTR</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">ASN</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">TTL</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-slate-700">
{% for record in dnsRecords['AAAA'] %}
{% set ipInfo = dnsIpDetails[record.value]|default(null) %}
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700">
<td class="px-4 py-2 text-xs font-medium text-gray-900 dark:text-white">
{% if record.host == '@' %}
<span class="text-indigo-600 dark:text-indigo-400">@ (root)</span>
{% else %}
{{ record.host }}
{% endif %}
</td>
<td class="px-4 py-2 text-xs">
<span class="font-mono text-gray-900 dark:text-white">{{ record.value }}</span>
{% if record.is_cloudflare %}
<i class="fas fa-cloud text-orange-500 dark:text-orange-400 ml-1.5" style="font-size: 10px;" title="Cloudflare"></i>
{% endif %}
</td>
<td class="px-4 py-2 text-xs font-mono text-gray-600 dark:text-slate-400 max-w-[200px] truncate" title="{{ ipInfo.reverse|default('') }}">
{{ ipInfo.reverse|default('-') }}
</td>
<td class="px-4 py-2.5">
{% if ipInfo and ipInfo.as %}
<div class="flex items-center gap-2">
{% if ipInfo.countryCode %}
<span class="fi fi-{{ ipInfo.countryCode|lower }}" style="font-size: 16px;"></span>
{% endif %}
<div class="text-xs">
<div class="font-semibold text-gray-900 dark:text-white">{{ ipInfo.as|split(' ')|first }}</div>
<div class="text-gray-600 dark:text-slate-400">{{ ipInfo.org|default(ipInfo.isp|default('')) }}</div>
{% if ipInfo.city or ipInfo.regionName %}
<div class="text-gray-500 dark:text-slate-500">{{ ipInfo.city|default('') }}{% if ipInfo.city and ipInfo.regionName %}, {% endif %}{{ ipInfo.regionName|default('') }}</div>
{% endif %}
</div>
</div>
{% else %}
<span class="text-gray-400 dark:text-slate-500 text-xs">-</span>
{% endif %}
</td>
<td class="px-4 py-2 text-xs text-gray-600 dark:text-slate-400">{{ record.ttl }}s</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{# ===== CNAME Records ===== #}
{% if dnsRecords['CNAME'] is defined and dnsRecords['CNAME']|length > 0 %}
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 bg-amber-50 dark:bg-amber-500/10 border-b border-amber-200 dark:border-amber-800">
<h3 class="text-xs font-semibold text-gray-900 dark:text-white flex items-center">
<i class="fas fa-link text-amber-600 dark:text-amber-400 mr-2" style="font-size: 10px;"></i>
CNAME Records (Aliases)
<span class="ml-2 px-1.5 py-0.5 bg-amber-600 text-white text-xs font-semibold rounded">{{ dnsRecords['CNAME']|length }}</span>
</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full">
<thead class="bg-gray-50 dark:bg-slate-900">
<tr>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Alias</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Target</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">TTL</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-slate-700">
{% for record in dnsRecords['CNAME'] %}
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700">
<td class="px-4 py-2 text-xs font-mono text-gray-900 dark:text-white">{{ record.host }}</td>
<td class="px-4 py-2 text-xs font-mono text-blue-600 dark:text-blue-400">{{ record.value }}</td>
<td class="px-4 py-2 text-xs text-gray-600 dark:text-slate-400">{{ record.ttl }}s</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{# ===== MX Records ===== #}
{% if dnsRecords['MX'] is defined and dnsRecords['MX']|length > 0 %}
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 bg-green-50 dark:bg-green-500/10 border-b border-green-200 dark:border-green-800">
<h3 class="text-xs font-semibold text-gray-900 dark:text-white flex items-center">
<i class="fas fa-envelope text-green-600 dark:text-green-400 mr-2" style="font-size: 10px;"></i>
MX Records (Mail Exchange)
<span class="ml-2 px-1.5 py-0.5 bg-green-600 text-white text-xs font-semibold rounded">{{ dnsRecords['MX']|length }}</span>
</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full">
<thead class="bg-gray-50 dark:bg-slate-900">
<tr>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Priority</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Mail Server</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">TTL</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-slate-700">
{% for record in dnsRecords['MX'] %}
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700">
<td class="px-4 py-2">
<span class="inline-flex items-center justify-center w-6 h-6 bg-green-100 dark:bg-green-500/10 text-green-800 dark:text-green-400 font-bold rounded-full text-xs">{{ record.priority }}</span>
</td>
<td class="px-4 py-2 text-xs font-mono text-gray-900 dark:text-white">{{ record.value }}</td>
<td class="px-4 py-2 text-xs text-gray-600 dark:text-slate-400">{{ record.ttl }}s</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{# ===== TXT Records ===== #}
{% if dnsRecords['TXT'] is defined and dnsRecords['TXT']|length > 0 %}
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 bg-purple-50 dark:bg-purple-500/10 border-b border-purple-200 dark:border-purple-800">
<h3 class="text-xs font-semibold text-gray-900 dark:text-white flex items-center">
<i class="fas fa-file-alt text-purple-600 dark:text-purple-400 mr-2" style="font-size: 10px;"></i>
TXT Records
<span class="ml-2 px-1.5 py-0.5 bg-purple-600 text-white text-xs font-semibold rounded">{{ dnsRecords['TXT']|length }}</span>
</h3>
</div>
<div class="p-4 space-y-2">
{% for record in dnsRecords['TXT'] %}
<div class="bg-gray-50 dark:bg-slate-700 rounded p-2 border border-gray-200 dark:border-slate-600">
<div class="flex items-start">
{% set val = record.value|lower %}
{% if val starts with 'v=spf1' %}
{% set txtType = 'SPF' %}
{% elseif val starts with 'v=dkim1' %}
{% set txtType = 'DKIM' %}
{% elseif val starts with 'v=dmarc1' %}
{% set txtType = 'DMARC' %}
{% elseif 'google-site-verification' in val %}
{% set txtType = 'Google' %}
{% elseif 'ms=' in val %}
{% set txtType = 'Microsoft' %}
{% elseif 'facebook-domain-verification' in val %}
{% set txtType = 'Facebook' %}
{% else %}
{% set txtType = 'TXT' %}
{% endif %}
<span class="inline-flex items-center px-1.5 py-0.5 bg-purple-100 dark:bg-purple-500/10 text-purple-800 dark:text-purple-400 text-xs font-semibold rounded mr-2 flex-shrink-0">{{ txtType }}</span>
<p class="flex-1 text-xs font-mono text-gray-900 dark:text-white break-all">{{ record.value }}</p>
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
{# ===== NS Records ===== #}
{% if dnsRecords['NS'] is defined and dnsRecords['NS']|length > 0 %}
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 bg-teal-50 dark:bg-teal-500/10 border-b border-teal-200 dark:border-teal-800">
<h3 class="text-xs font-semibold text-gray-900 dark:text-white flex items-center">
<i class="fas fa-server text-teal-600 dark:text-teal-400 mr-2" style="font-size: 10px;"></i>
NS Records (Name Servers)
<span class="ml-2 px-1.5 py-0.5 bg-teal-600 text-white text-xs font-semibold rounded">{{ dnsRecords['NS']|length }}</span>
</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full">
<thead class="bg-gray-50 dark:bg-slate-900">
<tr>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">#</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Nameserver</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">IPv4</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">IPv6</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">TTL</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-slate-700">
{% for record in dnsRecords['NS'] %}
{% set rawData = record.raw_data ? record.raw_data|from_json : null %}
{% set nsIps = rawData ? rawData._ns_ips|default(null) : null %}
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700">
<td class="px-4 py-2">
<div class="w-6 h-6 bg-teal-500 rounded flex items-center justify-center text-white font-bold text-xs">{{ loop.index }}</div>
</td>
<td class="px-4 py-2 text-xs font-mono text-gray-900 dark:text-white">{{ record.value }}</td>
<td class="px-4 py-2 text-xs font-mono text-gray-600 dark:text-slate-400">
{% if nsIps and nsIps.ipv4|default([])|length > 0 %}
{{ nsIps.ipv4|join(', ') }}
{% else %}-{% endif %}
</td>
<td class="px-4 py-2 text-xs font-mono text-gray-600 dark:text-slate-400">
{% if nsIps and nsIps.ipv6|default([])|length > 0 %}
{{ nsIps.ipv6|join(', ') }}
{% else %}-{% endif %}
</td>
<td class="px-4 py-2 text-xs text-gray-600 dark:text-slate-400">{{ record.ttl }}s</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{# ===== SRV Records ===== #}
{% if dnsRecords['SRV'] is defined and dnsRecords['SRV']|length > 0 %}
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 bg-violet-50 dark:bg-violet-500/10 border-b border-violet-200 dark:border-violet-800">
<h3 class="text-xs font-semibold text-gray-900 dark:text-white flex items-center">
<i class="fas fa-project-diagram text-violet-600 dark:text-violet-400 mr-2" style="font-size: 10px;"></i>
SRV Records (Services)
<span class="ml-2 px-1.5 py-0.5 bg-violet-600 text-white text-xs font-semibold rounded">{{ dnsRecords['SRV']|length }}</span>
</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full">
<thead class="bg-gray-50 dark:bg-slate-900">
<tr>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Service</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Target</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Port</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Priority</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Weight</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">TTL</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-slate-700">
{% for record in dnsRecords['SRV'] %}
{% set rawData = record.raw_data ? record.raw_data|from_json : {} %}
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700">
<td class="px-4 py-2 text-xs font-mono text-gray-900 dark:text-white">{{ record.host }}</td>
<td class="px-4 py-2 text-xs font-mono text-blue-600 dark:text-blue-400">{{ record.value }}</td>
<td class="px-4 py-2 text-xs text-gray-900 dark:text-white font-semibold">{{ rawData.port|default('-') }}</td>
<td class="px-4 py-2 text-xs text-gray-600 dark:text-slate-400">{{ record.priority|default('-') }}</td>
<td class="px-4 py-2 text-xs text-gray-600 dark:text-slate-400">{{ rawData.weight|default('-') }}</td>
<td class="px-4 py-2 text-xs text-gray-600 dark:text-slate-400">{{ record.ttl }}s</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{# ===== CAA Records ===== #}
{% if dnsRecords['CAA'] is defined and dnsRecords['CAA']|length > 0 %}
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 bg-orange-50 dark:bg-orange-500/10 border-b border-orange-200 dark:border-orange-800">
<h3 class="text-xs font-semibold text-gray-900 dark:text-white flex items-center">
<i class="fas fa-certificate text-orange-600 dark:text-orange-400 mr-2" style="font-size: 10px;"></i>
CAA Records (Certificate Authority)
<span class="ml-2 px-1.5 py-0.5 bg-orange-600 text-white text-xs font-semibold rounded">{{ dnsRecords['CAA']|length }}</span>
</h3>
</div>
<div class="overflow-x-auto">
<table class="min-w-full">
<thead class="bg-gray-50 dark:bg-slate-900">
<tr>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Tag</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Value (CA)</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Flags</th>
<th class="px-4 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">TTL</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-200 dark:divide-slate-700">
{% for record in dnsRecords['CAA'] %}
{% set rawData = record.raw_data ? record.raw_data|from_json : {} %}
<tr class="hover:bg-gray-50 dark:hover:bg-slate-700">
<td class="px-4 py-2">
<span class="inline-flex items-center px-1.5 py-0.5 bg-orange-100 dark:bg-orange-500/10 text-orange-800 dark:text-orange-400 text-xs font-semibold rounded">{{ rawData.tag|default('-') }}</span>
</td>
<td class="px-4 py-2 text-xs font-mono text-gray-900 dark:text-white">{{ rawData.value|default(record.value) }}</td>
<td class="px-4 py-2 text-xs text-gray-600 dark:text-slate-400">{{ rawData.flags|default('0') }}</td>
<td class="px-4 py-2 text-xs text-gray-600 dark:text-slate-400">{{ record.ttl }}s</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
</div>
{% endif %}
{% endif %}
<script>
function handleDnsRefresh(form) {
var btn = form.querySelector('.dns-refresh-btn');
if (btn.disabled) return false;
btn.disabled = true;
btn.classList.remove('bg-green-600', 'hover:bg-green-700');
btn.classList.add('bg-gray-400', 'cursor-not-allowed');
var icon = btn.querySelector('i');
var label = btn.querySelector('.btn-label');
if (icon) icon.classList.add('fa-spin');
if (label) label.textContent = 'Scanning DNS...';
return true;
}
</script>

View File

@@ -0,0 +1,131 @@
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900 flex flex-wrap items-center justify-between gap-2">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
<i class="fas fa-history text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Notification History
<span id="notification-count" class="ml-1.5 text-gray-600 dark:text-slate-400">({{ logs|length }})</span>
</h3>
{% if logs is not empty %}
<div class="flex flex-wrap items-center gap-2" id="notification-filters">
<select id="filter-channel" class="notification-filter text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 bg-white dark:bg-slate-800 text-gray-700 dark:text-slate-300 focus:ring-1 focus:ring-primary focus:border-primary">
<option value="">All channels</option>
<option value="email">Email</option>
<option value="telegram">Telegram</option>
<option value="discord">Discord</option>
<option value="slack">Slack</option>
<option value="mattermost">Mattermost</option>
<option value="webhook">Webhook</option>
<option value="pushover">Pushover</option>
</select>
<select id="filter-status" class="notification-filter text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 bg-white dark:bg-slate-800 text-gray-700 dark:text-slate-300 focus:ring-1 focus:ring-primary focus:border-primary">
<option value="">All statuses</option>
<option value="sent">Sent</option>
<option value="failed">Failed</option>
</select>
<select id="filter-type" class="notification-filter text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 bg-white dark:bg-slate-800 text-gray-700 dark:text-slate-300 focus:ring-1 focus:ring-primary focus:border-primary">
<option value="">All types</option>
<option value="expiration">Expiration</option>
<option value="status">Status change</option>
<option value="dns">DNS change</option>
</select>
<input type="text" id="filter-search" placeholder="Search message..." class="notification-filter text-xs border border-gray-300 dark:border-slate-600 rounded px-2 py-1 bg-white dark:bg-slate-800 text-gray-700 dark:text-slate-300 w-32 focus:ring-1 focus:ring-primary focus:border-primary" />
<button type="button" id="filter-reset" class="text-xs text-gray-500 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-200 px-2 py-1" title="Reset filters">Clear</button>
</div>
{% endif %}
</div>
<div class="overflow-hidden">
{% if logs is empty %}
<div class="p-8 text-center">
<i class="fas fa-bell-slash text-gray-300 dark:text-slate-600 text-3xl mb-2"></i>
<p class="text-xs text-gray-500 dark:text-slate-400">No notifications sent yet</p>
</div>
{% else %}
<div id="notification-table-wrap" class="max-h-96 overflow-y-auto">
<table class="min-w-full divide-y divide-gray-200 dark:divide-slate-700 text-xs">
<thead class="bg-gray-50 dark:bg-slate-900 sticky top-0">
<tr>
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Channel</th>
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Status</th>
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Date</th>
<th class="px-3 py-2 text-left text-xs font-semibold text-gray-600 dark:text-slate-400 uppercase">Message</th>
</tr>
</thead>
<tbody class="bg-white dark:bg-slate-800 divide-y divide-gray-200 dark:divide-slate-700" id="notification-log-tbody">
{% for log in logs %}
{% set nt = log.notification_type|default('') %}
{% set logType = (nt == 'expired' or (nt|slice(0, 13)) == 'expiring_in_') ? 'expiration' : (((nt|slice(0, 7)) == 'domain_') ? 'status' : ((nt == 'dns_change') ? 'dns' : 'other')) %}
<tr class="notification-log-row hover:bg-gray-50 dark:hover:bg-slate-700" data-channel="{{ log.channel_type }}" data-status="{{ log.status }}" data-type="{{ logType }}" data-message="{{ log.message|e('html_attr') }}">
<td class="px-3 py-2 whitespace-nowrap">
<span class="px-2 py-0.5 rounded text-xs font-medium bg-blue-100 dark:bg-blue-500/10 text-blue-800 dark:text-blue-400">
{{ log.channel_type|capitalize }}
</span>
</td>
<td class="px-3 py-2 whitespace-nowrap">
{% set logStatusClass = log.status == 'sent' ? 'bg-green-100 dark:bg-green-500/10 text-green-800 dark:text-green-400' : 'bg-red-100 dark:bg-red-500/10 text-red-800 dark:text-red-400' %}
<span class="px-2 py-0.5 rounded text-xs font-medium {{ logStatusClass }}">
{{ log.status|capitalize }}
</span>
</td>
<td class="px-3 py-2 whitespace-nowrap text-gray-600 dark:text-slate-400">{{ log.sent_at|date('M j, H:i') }}</td>
<td class="px-3 py-2 text-gray-700 dark:text-slate-300 max-w-xs truncate" title="{{ log.message }}">
{{ log.message }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div id="notification-empty-filter" class="hidden p-8 text-center">
<i class="fas fa-filter text-gray-300 dark:text-slate-600 text-3xl mb-2"></i>
<p class="text-xs text-gray-500 dark:text-slate-400">No notifications match the current filters</p>
</div>
{% endif %}
</div>
</div>
{% if logs is not empty %}
<script>
(function() {
var rows = document.querySelectorAll('.notification-log-row');
var countEl = document.getElementById('notification-count');
var emptyFilterEl = document.getElementById('notification-empty-filter');
function applyFilters() {
var channel = document.getElementById('filter-channel').value;
var status = document.getElementById('filter-status').value;
var type = document.getElementById('filter-type').value;
var search = (document.getElementById('filter-search').value || '').toLowerCase();
var visible = 0;
rows.forEach(function(row) {
var match = true;
if (channel && row.dataset.channel !== channel) match = false;
if (status && row.dataset.status !== status) match = false;
if (type && row.dataset.type !== type) match = false;
if (search && row.dataset.message.toLowerCase().indexOf(search) === -1) match = false;
row.style.display = match ? '' : 'none';
if (match) visible++;
});
countEl.textContent = '(' + visible + (visible !== rows.length ? ' of ' + rows.length + ')' : ')');
emptyFilterEl.classList.toggle('hidden', visible > 0);
document.getElementById('notification-table-wrap').classList.toggle('hidden', visible === 0);
}
function resetFilters() {
document.getElementById('filter-channel').value = '';
document.getElementById('filter-status').value = '';
document.getElementById('filter-type').value = '';
document.getElementById('filter-search').value = '';
applyFilters();
}
document.querySelectorAll('.notification-filter').forEach(function(el) {
el.addEventListener('change', applyFilters);
el.addEventListener('input', function() { if (el.id === 'filter-search') applyFilters(); });
});
document.getElementById('filter-reset').addEventListener('click', resetFilters);
})();
</script>
{% endif %}

View File

@@ -0,0 +1,284 @@
<!-- OVERVIEW TAB CONTENT -->
<!-- Main 2-Column Layout -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-3">
<!-- LEFT COLUMN -->
<div class="space-y-3">
<!-- Domain Info Card -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
<i class="fas fa-info-circle text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Domain Information
</h3>
</div>
<div class="p-4">
<div class="space-y-2 text-xs">
<div class="flex justify-between">
<span class="text-gray-500 dark:text-slate-400">Registrar:</span>
<span class="text-gray-900 dark:text-white font-medium">{{ domain.registrar ?? 'Unknown' }}</span>
</div>
{% if domain.registrar_url is not empty %}
<div class="flex justify-between">
<span class="text-gray-500 dark:text-slate-400">Registrar URL:</span>
<a href="{{ domain.registrar_url }}" target="_blank" class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 flex items-center">
<i class="fas fa-external-link-alt mr-1" style="font-size: 9px;"></i>
Visit
</a>
</div>
{% endif %}
<div class="flex justify-between">
<span class="text-gray-500 dark:text-slate-400">Expires:</span>
{% set expiryColor = domain.expiryColor|default('gray') %}
<span class="text-{{ expiryColor }}-600 dark:text-{{ expiryColor }}-400 font-semibold">
{% if domain.expiration_date is defined and domain.expiration_date %}
{{ domain.expiration_date|date('M d, Y') }}{% if domain.daysLeft is defined and domain.daysLeft is not null %} ({{ domain.daysLeft }} days){% endif %}
{% if domain.isManualExpiration %}
<span class="ml-1 inline-flex items-center px-1.5 py-0.5 rounded text-xs font-medium bg-amber-100 dark:bg-amber-500/10 text-amber-800 dark:text-amber-400">
<i class="fas fa-edit mr-0.5" style="font-size: 8px;"></i>Manual
</span>
{% endif %}
{% else %}
Unknown
{% endif %}
</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500 dark:text-slate-400">Created:</span>
<span class="text-gray-900 dark:text-white">{% if whoisData.creation_date is defined %}{{ whoisData.creation_date|date('M d, Y') }}{% else %}-{% endif %}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500 dark:text-slate-400">Last Updated:</span>
<span class="text-gray-900 dark:text-white">{% if domain.updated_date is defined and domain.updated_date %}{{ domain.updated_date|date('M d, Y') }}{% else %}-{% endif %}</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500 dark:text-slate-400">Last Checked:</span>
<span class="text-gray-900 dark:text-white">{% if domain.last_checked is defined and domain.last_checked %}{{ domain.last_checked|date('M d, Y H:i') }}{% else %}-{% endif %}</span>
</div>
</div>
</div>
</div>
<!-- Financial Summary (Mockup) -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900 flex items-center justify-between">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
<i class="fas fa-dollar-sign text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Financial Summary
</h3>
<button onclick="switchTab('billing')" class="text-xs text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300">
Details
<i class="fas fa-arrow-right ml-1" style="font-size: 8px;"></i>
</button>
</div>
<div class="p-4">
<div class="space-y-2 text-xs">
<div class="flex justify-between">
<span class="text-gray-500 dark:text-slate-400">Purchase Price:</span>
<span class="text-gray-900 dark:text-white font-medium">$12.99</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500 dark:text-slate-400">Renewal Cost:</span>
<span class="text-gray-900 dark:text-white font-medium">$14.99 / yr</span>
</div>
<div class="flex justify-between">
<span class="text-gray-500 dark:text-slate-400">Total Spent:</span>
<span class="text-gray-900 dark:text-white font-semibold">$42.97</span>
</div>
<div class="border-t border-gray-100 dark:border-slate-700 pt-2 mt-2">
<div class="flex justify-between items-center">
<span class="text-gray-500 dark:text-slate-400">Next Renewal:</span>
{% if domain.expiration_date is defined and domain.expiration_date %}
<span class="inline-flex items-center px-2 py-0.5 bg-{{ (domain.expiryColor ?? 'gray') }}-100 dark:bg-{{ (domain.expiryColor ?? 'gray') }}-500/10 text-{{ (domain.expiryColor ?? 'gray') }}-800 dark:text-{{ (domain.expiryColor ?? 'gray') }}-400 text-xs font-semibold rounded">
{{ domain.expiration_date|date('M d, Y') }}
</span>
{% else %}
<span class="text-gray-400 dark:text-slate-500">-</span>
{% endif %}
</div>
</div>
</div>
<div class="mt-3 px-2 py-1.5 bg-amber-50 dark:bg-amber-500/10 rounded border border-amber-200 dark:border-amber-800">
<p class="text-xs text-amber-700 dark:text-amber-400 flex items-center">
<i class="fas fa-info-circle mr-1.5" style="font-size: 9px;"></i>
Sample data &mdash; billing features coming soon
</p>
</div>
</div>
</div>
<!-- Notes (Inline Editable) -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900 flex items-center justify-between">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
<i class="fas fa-sticky-note text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Notes
</h3>
<button id="notes-edit-btn" onclick="toggleNotesEdit(true)" class="text-xs text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300">
<i class="fas fa-edit mr-1" style="font-size: 9px;"></i>
Edit
</button>
</div>
<div class="p-4">
<!-- View Mode -->
<div id="notes-view-mode">
{% if domain.notes is not empty %}
<div class="text-xs text-gray-900 dark:text-white whitespace-pre-wrap font-mono bg-gray-50 dark:bg-slate-900 rounded p-2 border border-gray-200 dark:border-slate-700 max-h-40 overflow-y-auto">{{ domain.notes }}</div>
{% else %}
<p class="text-xs text-gray-500 dark:text-slate-400 italic">No notes yet. <button onclick="toggleNotesEdit(true)" class="text-blue-600 dark:text-blue-400 hover:underline">Add notes</button></p>
{% endif %}
</div>
<!-- Edit Mode -->
<div id="notes-edit-mode" class="hidden">
<form method="POST" action="/domains/{{ domain.id }}/update-notes" id="overview-notes-form">
{{ csrf_field()|raw }}
<textarea
name="notes"
id="overview-notes-textarea"
rows="6"
class="w-full px-3 py-2 text-xs border border-gray-300 dark:border-slate-600 rounded-lg bg-white dark:bg-slate-900 text-gray-900 dark:text-white focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent resize-none"
placeholder="Add notes about this domain...">{{ domain.notes|default('') }}</textarea>
<div class="flex gap-2 mt-2">
<button
type="submit"
class="flex-1 inline-flex items-center justify-center px-3 py-1.5 bg-blue-600 text-white text-xs rounded-lg hover:bg-blue-700 transition-colors font-medium">
<i class="fas fa-save mr-1.5"></i>
Save
</button>
<button
type="button"
onclick="toggleNotesEdit(false)"
class="flex-1 inline-flex items-center justify-center px-3 py-1.5 border border-gray-300 dark:border-slate-600 text-gray-700 dark:text-slate-300 text-xs rounded-lg hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors font-medium">
<i class="fas fa-times mr-1.5"></i>
Cancel
</button>
</div>
</form>
</div>
</div>
</div>
</div>
<!-- RIGHT COLUMN -->
<div class="space-y-3">
<!-- Monitoring Status -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
<i class="fas fa-heartbeat text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Monitoring Status
</h3>
</div>
<div class="p-4">
<div class="space-y-2">
<div class="flex items-center justify-between">
<div class="flex items-center">
<i class="fas fa-file-alt text-blue-500 dark:text-blue-400 mr-2" style="font-size: 10px;"></i>
<span class="text-xs text-gray-700 dark:text-slate-300">WHOIS</span>
</div>
{% if domain.is_active %}
<span class="inline-flex items-center px-2 py-0.5 bg-green-100 dark:bg-green-500/10 text-green-800 dark:text-green-400 text-xs font-semibold rounded">
<i class="fas fa-check-circle mr-1" style="font-size: 9px;"></i>Active
</span>
{% else %}
<span class="inline-flex items-center px-2 py-0.5 bg-gray-100 dark:bg-slate-700 text-gray-600 dark:text-slate-400 text-xs font-semibold rounded">
<i class="fas fa-pause-circle mr-1" style="font-size: 9px;"></i>Disabled
</span>
{% endif %}
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<i class="fas fa-lock text-indigo-500 dark:text-indigo-400 mr-2" style="font-size: 10px;"></i>
<span class="text-xs text-gray-700 dark:text-slate-300">SSL</span>
</div>
<span class="inline-flex items-center px-2 py-0.5 bg-gray-100 dark:bg-slate-700 text-gray-500 dark:text-slate-400 text-xs font-semibold rounded">
<i class="fas fa-minus-circle mr-1" style="font-size: 9px;"></i>Coming Soon
</span>
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<i class="fas fa-network-wired text-blue-500 dark:text-blue-400 mr-2" style="font-size: 10px;"></i>
<span class="text-xs text-gray-700 dark:text-slate-300">DNS</span>
</div>
{% if domain.dns_monitoring_enabled|default(1) %}
<span class="inline-flex items-center px-2 py-0.5 bg-green-100 dark:bg-green-500/10 text-green-800 dark:text-green-400 text-xs font-semibold rounded">
<i class="fas fa-check-circle mr-1" style="font-size: 9px;"></i>Active
</span>
{% else %}
<span class="inline-flex items-center px-2 py-0.5 bg-gray-100 dark:bg-slate-700 text-gray-600 dark:text-slate-400 text-xs font-semibold rounded">
<i class="fas fa-pause-circle mr-1" style="font-size: 9px;"></i>Disabled
</span>
{% endif %}
</div>
<div class="flex items-center justify-between">
<div class="flex items-center">
<i class="fas fa-bell text-orange-500 dark:text-orange-400 mr-2" style="font-size: 10px;"></i>
<span class="text-xs text-gray-700 dark:text-slate-300">Notification Group</span>
</div>
{% if domain.group_name is not empty %}
<span class="inline-flex items-center px-2 py-0.5 bg-blue-100 dark:bg-blue-500/10 text-blue-800 dark:text-blue-400 text-xs font-semibold rounded">
<i class="fas fa-bell mr-1" style="font-size: 9px;"></i>{{ domain.group_name }}
</span>
{% else %}
<a href="/domains/{{ domain.id }}/edit?from=/domains/{{ domain.id }}" class="inline-flex items-center px-2 py-0.5 bg-orange-100 dark:bg-orange-500/10 text-orange-800 dark:text-orange-400 text-xs font-semibold rounded hover:bg-orange-200 dark:hover:bg-orange-500/20 transition-colors">
<i class="fas fa-plus-circle mr-1" style="font-size: 9px;"></i>Assign Group
</a>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Active Channels -->
{% if domain.group_name is not empty %}
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
<i class="fas fa-bell text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Active Channels
<span class="ml-auto text-xs font-medium text-gray-500 dark:text-slate-400 normal-case tracking-normal">{{ domain.group_name }}</span>
</h3>
</div>
<div class="p-4">
{% if domain.channels is not empty %}
<div class="grid grid-cols-2 gap-2">
{% for channel in domain.channels %}
<div class="flex items-center p-2 rounded {{ channel.is_active ? 'bg-green-50 dark:bg-green-500/10 border border-green-200 dark:border-green-800' : 'bg-gray-50 dark:bg-slate-700 border border-gray-200 dark:border-slate-700' }}">
<i class="fas fa-{{ channel.is_active ? 'check-circle text-green-600 dark:text-green-400' : 'times-circle text-gray-400 dark:text-slate-500' }} mr-2 text-xs"></i>
<span class="text-xs font-medium text-gray-700 dark:text-slate-300">{{ channel.channel_type|capitalize }}</span>
</div>
{% endfor %}
</div>
<p class="text-xs text-gray-500 dark:text-slate-400 mt-2">{{ domain.activeChannelCount|default(0) }} of {{ domain.channels|length }} channels active</p>
{% else %}
<p class="text-xs text-gray-500 dark:text-slate-400 italic">No channels configured for this group</p>
{% endif %}
</div>
</div>
{% endif %}
<!-- Domain Status Codes -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
<i class="fas fa-shield-alt text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Domain Status Codes
{% if domain is defined and domain.parsedStatuses is defined and domain.parsedStatuses is not empty %}
<span class="ml-1.5 px-1.5 py-0.5 bg-blue-100 dark:bg-blue-500/10 text-blue-800 dark:text-blue-400 text-xs font-semibold rounded">{{ domain.parsedStatuses|length }}</span>
{% endif %}
</h3>
</div>
<div class="p-4">
<div class="flex flex-wrap gap-1.5">
{% if domain is defined and domain.parsedStatuses is defined and domain.parsedStatuses is not empty %}
{% for status in domain.parsedStatuses %}
<span class="px-2 py-1 bg-blue-100 dark:bg-blue-500/10 text-blue-800 dark:text-blue-400 rounded text-xs font-medium" title="{{ status }}">{{ status|replace({'_':' '})|title }}</span>
{% endfor %}
{% else %}
<span class="px-2 py-1 bg-gray-100 dark:bg-slate-700 text-gray-700 dark:text-slate-300 rounded text-xs font-medium">No status codes</span>
{% endif %}
</div>
</div>
</div>
</div>
</div>

View File

@@ -0,0 +1,379 @@
<!-- SSL TAB CONTENT -->
<!-- Preview Banner -->
<div class="mb-3 bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-800 rounded-lg p-3">
<div class="flex items-center">
<i class="fas fa-flask text-amber-600 dark:text-amber-400 mr-2" style="font-size: 12px;"></i>
<div>
<p class="text-xs font-semibold text-amber-900 dark:text-amber-300">Preview</p>
<p class="text-xs text-amber-800 dark:text-amber-400 mt-0.5">SSL certificate monitoring is coming soon. This is a design preview with sample data.</p>
</div>
</div>
</div>
<!-- Filters & Actions Bar -->
<div class="mb-3 flex flex-wrap gap-3 justify-between items-center">
<div class="flex-1 max-w-md">
<div class="relative">
<input type="text" id="ssl-search" placeholder="Search certificates..." class="w-full pl-9 pr-3 py-2 border border-gray-300 dark:border-slate-600 bg-white dark:bg-slate-900 text-gray-900 dark:text-white 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 dark:text-slate-500 text-xs"></i>
</div>
</div>
<div class="flex gap-2">
<select id="ssl-filter" class="px-3 py-2 border border-gray-300 dark:border-slate-600 bg-white dark:bg-slate-900 text-gray-900 dark:text-white rounded-lg text-sm">
<option value="all">All Certificates</option>
<option value="valid">Valid Only</option>
<option value="expiring">Expiring Soon</option>
<option value="expired">Expired</option>
<option value="invalid">Invalid</option>
</select>
<button onclick="checkAllCertificates()" class="inline-flex items-center px-3 py-2 bg-green-600 text-white text-xs rounded-lg hover:bg-green-700 transition-colors font-medium">
<i class="fas fa-sync-alt mr-1.5" style="font-size: 10px;"></i>
Check All
</button>
<button class="inline-flex items-center px-3 py-2 bg-primary text-white text-xs rounded-lg hover:bg-primary-dark transition-colors font-medium">
<i class="fas fa-plus mr-1.5" style="font-size: 10px;"></i>
Add Subdomain
</button>
</div>
</div>
<!-- Bulk Actions Toolbar (Hidden by default) -->
<div id="ssl-bulk-actions" class="hidden mb-3 bg-blue-50 dark:bg-blue-500/10 border border-blue-200 dark:border-blue-800 rounded-lg p-3">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<span id="ssl-selected-count" class="text-xs font-medium text-blue-900 dark:text-blue-300"></span>
<button type="button" onclick="bulkCheckSSL()" class="inline-flex items-center px-3 py-1.5 bg-green-600 text-white text-xs rounded-lg hover:bg-green-700 transition-colors font-medium">
<i class="fas fa-sync-alt mr-1.5" style="font-size: 9px;"></i>
Check Selected
</button>
<button type="button" onclick="bulkDeleteSSL()" class="inline-flex items-center px-3 py-1.5 bg-red-600 text-white text-xs rounded-lg hover:bg-red-700 transition-colors font-medium">
<i class="fas fa-trash mr-1.5" style="font-size: 9px;"></i>
Delete Selected
</button>
<button type="button" onclick="clearSSLSelection()" class="inline-flex items-center px-3 py-1.5 border border-gray-300 dark:border-slate-600 bg-white dark:bg-slate-800 text-gray-700 dark:text-slate-300 text-xs rounded-lg hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors font-medium">
<i class="fas fa-times mr-1.5" style="font-size: 9px;"></i>
Clear Selection
</button>
</div>
</div>
</div>
<!-- SSL Statistics -->
<div class="grid grid-cols-1 md:grid-cols-4 gap-3 mb-3">
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-3">
<div class="flex items-center justify-between">
<div>
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase">Total</p>
<p class="text-lg font-semibold text-gray-900 dark:text-white mt-0.5">3</p>
</div>
<i class="fas fa-lock text-gray-400 dark:text-slate-500" style="font-size: 18px;"></i>
</div>
</div>
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-3">
<div class="flex items-center justify-between">
<div>
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase">Valid</p>
<p class="text-lg font-semibold text-green-600 dark:text-green-400 mt-0.5">2</p>
</div>
<i class="fas fa-check-circle text-green-500 dark:text-green-400" style="font-size: 18px;"></i>
</div>
</div>
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-3">
<div class="flex items-center justify-between">
<div>
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase">Expiring Soon</p>
<p class="text-lg font-semibold text-orange-600 dark:text-orange-400 mt-0.5">1</p>
</div>
<i class="fas fa-exclamation-triangle text-orange-500 dark:text-orange-400" style="font-size: 18px;"></i>
</div>
</div>
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 p-3">
<div class="flex items-center justify-between">
<div>
<p class="text-xs font-medium text-gray-500 dark:text-slate-400 uppercase">Invalid</p>
<p class="text-lg font-semibold text-red-600 dark:text-red-400 mt-0.5">1</p>
</div>
<i class="fas fa-times-circle text-red-500 dark:text-red-400" style="font-size: 18px;"></i>
</div>
</div>
</div>
<!-- Pagination Info -->
<div class="mb-3 flex justify-between items-center">
<div class="text-xs text-gray-600 dark:text-slate-400">
Showing <span class="font-semibold text-gray-900 dark:text-white">1</span> to <span class="font-semibold text-gray-900 dark:text-white">3</span> of <span class="font-semibold text-gray-900 dark:text-white">3</span> certificate(s)
</div>
<div class="flex items-center gap-2">
<label class="text-xs text-gray-600 dark:text-slate-400">Show:</label>
<select class="px-2 py-1 border border-gray-300 dark:border-slate-600 bg-white dark:bg-slate-900 text-gray-900 dark:text-white rounded text-xs">
<option>10</option>
<option selected>25</option>
<option>50</option>
</select>
</div>
</div>
<!-- SSL Certificates List -->
<div class="space-y-3">
<!-- Cert 1 (root) -->
<div class="bg-white dark:bg-slate-800 rounded-lg border-2 border-green-200 dark:border-green-800 overflow-hidden ssl-cert-item" data-cert-id="1" data-status="valid">
<div class="px-4 py-2 bg-green-50 dark:bg-green-500/10 border-b border-green-200 dark:border-green-800">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<i class="fas fa-lock text-green-600 dark:text-green-400 mr-2" style="font-size: 14px;"></i>
<div>
<h3 class="text-xs font-semibold text-gray-900 dark:text-white">example.com <span class="ml-2 px-1.5 py-0.5 bg-primary text-white text-xs font-semibold rounded">Root</span></h3>
<p class="text-xs text-gray-500 dark:text-slate-400 mt-0.5">Certificate monitoring active</p>
</div>
</div>
<span class="inline-flex items-center px-2 py-1 bg-green-100 dark:bg-green-500/10 text-green-800 dark:text-green-400 text-xs font-semibold rounded border border-green-200 dark:border-green-800">
<i class="fas fa-check-circle mr-1" style="font-size: 9px;"></i>
Valid & Trusted
</span>
</div>
</div>
<div class="p-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="space-y-3">
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Validity Period</label>
<div class="mt-1.5 space-y-1.5">
<div class="flex justify-between items-center"><span class="text-xs text-gray-600 dark:text-slate-400">Issued:</span><span class="text-xs font-medium text-gray-900 dark:text-white">Oct 05, 2025</span></div>
<div class="flex justify-between items-center"><span class="text-xs text-gray-600 dark:text-slate-400">Expires:</span><span class="text-xs font-semibold text-green-700 dark:text-green-400">Jan 08, 2026</span></div>
<div class="flex justify-between items-center"><span class="text-xs text-gray-600 dark:text-slate-400">Valid for:</span><span class="text-xs font-bold text-green-600 dark:text-green-400">65 days</span></div>
</div>
</div>
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Certificate Authority</label>
<div class="mt-1.5">
<p class="text-xs text-gray-900 dark:text-white font-medium">Let's Encrypt Authority X3</p>
<p class="text-xs text-gray-500 dark:text-slate-400 mt-0.5">✓ Trusted CA</p>
</div>
</div>
</div>
<div class="space-y-3">
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Covered Domains (SANs)</label>
<div class="mt-1.5 space-y-1">
<div class="flex items-center text-xs"><i class="fas fa-check text-green-500 dark:text-green-400 mr-1.5" style="font-size: 9px;"></i><span class="text-gray-900 dark:text-white font-mono">example.com</span></div>
<div class="flex items-center text-xs"><i class="fas fa-check text-green-500 dark:text-green-400 mr-1.5" style="font-size: 9px;"></i><span class="text-gray-900 dark:text-white font-mono">www.example.com</span></div>
</div>
</div>
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Security Details</label>
<div class="mt-1.5 space-y-1.5">
<div class="flex justify-between"><span class="text-xs text-gray-600 dark:text-slate-400">Signature:</span><span class="text-xs font-medium text-gray-900 dark:text-white">SHA256-RSA</span></div>
<div class="flex justify-between"><span class="text-xs text-gray-600 dark:text-slate-400">Key Size:</span><span class="text-xs font-medium text-gray-900 dark:text-white">2048 bits</span></div>
<div class="flex justify-between"><span class="text-xs text-gray-600 dark:text-slate-400">Version:</span><span class="text-xs font-medium text-gray-900 dark:text-white">v3</span></div>
</div>
</div>
</div>
</div>
<div class="flex items-center justify-between mt-4 pt-4 border-t border-gray-200 dark:border-slate-700">
<div class="text-xs text-gray-500 dark:text-slate-400"><i class="far fa-clock mr-1"></i>Last checked: Today 10:00</div>
<div class="flex gap-2">
<button class="inline-flex items-center px-2 py-1 bg-green-600 text-white text-xs rounded hover:bg-green-700 transition-colors font-medium"><i class="fas fa-sync-alt mr-1" style="font-size: 9px;"></i>Check Now</button>
</div>
</div>
</div>
</div>
<!-- Cert 2 (mail subdomain) -->
<div class="bg-white dark:bg-slate-800 rounded-lg border-2 border-green-200 dark:border-green-800 overflow-hidden ssl-cert-item" data-cert-id="2" data-status="valid">
<div class="px-4 py-2 bg-green-50 dark:bg-green-500/10 border-b border-green-200 dark:border-green-800">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<input type="checkbox" class="ssl-checkbox rounded border-gray-300 dark:border-slate-600 text-primary focus:ring-primary" value="2" onchange="updateSSLBulkActions()">
<i class="fas fa-lock text-green-600 dark:text-green-400 mr-2" style="font-size: 14px;"></i>
<div>
<h3 class="text-xs font-semibold text-gray-900 dark:text-white">mail.example.com</h3>
<p class="text-xs text-gray-500 dark:text-slate-400 mt-0.5">Certificate monitoring active</p>
</div>
</div>
<span class="inline-flex items-center px-2 py-1 bg-green-100 dark:bg-green-500/10 text-green-800 dark:text-green-400 text-xs font-semibold rounded border border-green-200 dark:border-green-800">
<i class="fas fa-check-circle mr-1" style="font-size: 9px;"></i>
Valid & Trusted
</span>
</div>
</div>
<div class="p-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="space-y-3">
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Validity Period</label>
<div class="mt-1.5 space-y-1.5">
<div class="flex justify-between items-center"><span class="text-xs text-gray-600 dark:text-slate-400">Issued:</span><span class="text-xs font-medium text-gray-900 dark:text-white">Aug 01, 2025</span></div>
<div class="flex justify-between items-center"><span class="text-xs text-gray-600 dark:text-slate-400">Expires:</span><span class="text-xs font-semibold text-green-700 dark:text-green-400">Jul 28, 2026</span></div>
<div class="flex justify-between items-center"><span class="text-xs text-gray-600 dark:text-slate-400">Valid for:</span><span class="text-xs font-bold text-green-600 dark:text-green-400">270 days</span></div>
</div>
</div>
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Certificate Authority</label>
<div class="mt-1.5">
<p class="text-xs text-gray-900 dark:text-white font-medium">DigiCert Inc.</p>
<p class="text-xs text-gray-500 dark:text-slate-400 mt-0.5">✓ Trusted CA</p>
</div>
</div>
</div>
<div class="space-y-3">
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Covered Domains (SANs)</label>
<div class="mt-1.5 space-y-1">
<div class="flex items-center text-xs"><i class="fas fa-check text-green-500 dark:text-green-400 mr-1.5" style="font-size: 9px;"></i><span class="text-gray-900 dark:text-white font-mono">mail.example.com</span></div>
<div class="flex items-center text-xs"><i class="fas fa-check text-green-500 dark:text-green-400 mr-1.5" style="font-size: 9px;"></i><span class="text-gray-900 dark:text-white font-mono">smtp.example.com</span></div>
<div class="flex items-center text-xs"><i class="fas fa-check text-green-500 dark:text-green-400 mr-1.5" style="font-size: 9px;"></i><span class="text-gray-900 dark:text-white font-mono">imap.example.com</span></div>
</div>
</div>
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Security Details</label>
<div class="mt-1.5 space-y-1.5">
<div class="flex justify-between"><span class="text-xs text-gray-600 dark:text-slate-400">Signature:</span><span class="text-xs font-medium text-gray-900 dark:text-white">SHA256-RSA</span></div>
<div class="flex justify-between"><span class="text-xs text-gray-600 dark:text-slate-400">Key Size:</span><span class="text-xs font-medium text-gray-900 dark:text-white">2048 bits</span></div>
<div class="flex justify-between"><span class="text-xs text-gray-600 dark:text-slate-400">Version:</span><span class="text-xs font-medium text-gray-900 dark:text-white">v3</span></div>
</div>
</div>
</div>
</div>
<div class="flex items-center justify-between mt-4 pt-4 border-t border-gray-200 dark:border-slate-700">
<div class="text-xs text-gray-500 dark:text-slate-400"><i class="far fa-clock mr-1"></i>Last checked: Today 10:00</div>
<div class="flex gap-2">
<button class="inline-flex items-center px-2 py-1 bg-green-600 text-white text-xs rounded hover:bg-green-700 transition-colors font-medium"><i class="fas fa-sync-alt mr-1" style="font-size: 9px;"></i>Check Now</button>
<button class="inline-flex items-center px-2 py-1 bg-red-600 text-white text-xs rounded hover:bg-red-700 transition-colors font-medium"><i class="fas fa-trash mr-1" style="font-size: 9px;"></i>Remove</button>
</div>
</div>
</div>
</div>
<!-- Cert 3 (api - expired) -->
<div class="bg-white dark:bg-slate-800 rounded-lg border-2 border-red-200 dark:border-red-800 overflow-hidden ssl-cert-item" data-cert-id="3" data-status="expired">
<div class="px-4 py-2 bg-red-50 dark:bg-red-500/10 border-b border-red-200 dark:border-red-800">
<div class="flex items-center justify-between">
<div class="flex items-center gap-3">
<input type="checkbox" class="ssl-checkbox rounded border-gray-300 dark:border-slate-600 text-primary focus:ring-primary" value="3" onchange="updateSSLBulkActions()">
<i class="fas fa-lock text-red-600 dark:text-red-400 mr-2" style="font-size: 14px;"></i>
<div>
<h3 class="text-xs font-semibold text-gray-900 dark:text-white">api.example.com</h3>
<p class="text-xs text-gray-500 dark:text-slate-400 mt-0.5">Certificate monitoring active</p>
</div>
</div>
<span class="inline-flex items-center px-2 py-1 bg-red-100 dark:bg-red-500/10 text-red-800 dark:text-red-400 text-xs font-semibold rounded border border-red-200 dark:border-red-800">
<i class="fas fa-times-circle mr-1" style="font-size: 9px;"></i>
EXPIRED
</span>
</div>
</div>
<div class="p-4">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<div class="space-y-3">
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Validity Period</label>
<div class="mt-1.5 space-y-1.5">
<div class="flex justify-between items-center"><span class="text-xs text-gray-600 dark:text-slate-400">Issued:</span><span class="text-xs font-medium text-gray-900 dark:text-white">Sep 26, 2024</span></div>
<div class="flex justify-between items-center"><span class="text-xs text-gray-600 dark:text-slate-400">Expires:</span><span class="text-xs font-semibold text-red-700 dark:text-red-400">Sep 30, 2025</span></div>
<div class="flex justify-between items-center"><span class="text-xs text-gray-600 dark:text-slate-400">Valid for:</span><span class="text-xs font-bold text-red-600 dark:text-red-400">30 days (expired)</span></div>
</div>
</div>
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Certificate Authority</label>
<div class="mt-1.5">
<p class="text-xs text-gray-900 dark:text-white font-medium">Self-Signed</p>
<p class="text-xs text-gray-500 dark:text-slate-400 mt-0.5">⚠️ Not Trusted</p>
</div>
</div>
<div class="bg-red-50 dark:bg-red-500/10 border border-red-200 dark:border-red-800 rounded p-2">
<p class="text-xs font-semibold text-red-900 dark:text-red-300 mb-0.5">Error Details</p>
<p class="text-xs text-red-700 dark:text-red-400">Certificate has expired</p>
</div>
</div>
<div class="space-y-3">
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Covered Domains (SANs)</label>
<div class="mt-1.5 space-y-1">
<div class="flex items-center text-xs"><i class="fas fa-check text-green-500 dark:text-green-400 mr-1.5" style="font-size: 9px;"></i><span class="text-gray-900 dark:text-white font-mono">api.example.com</span></div>
</div>
</div>
<div>
<label class="text-xs font-semibold text-gray-500 dark:text-slate-400 uppercase tracking-wide">Security Details</label>
<div class="mt-1.5 space-y-1.5">
<div class="flex justify-between"><span class="text-xs text-gray-600 dark:text-slate-400">Signature:</span><span class="text-xs font-medium text-gray-900 dark:text-white">SHA256-RSA</span></div>
<div class="flex justify-between"><span class="text-xs text-gray-600 dark:text-slate-400">Key Size:</span><span class="text-xs font-medium text-gray-900 dark:text-white">2048 bits</span></div>
<div class="flex justify-between"><span class="text-xs text-gray-600 dark:text-slate-400">Version:</span><span class="text-xs font-medium text-gray-900 dark:text-white">v3</span></div>
</div>
</div>
</div>
</div>
<div class="flex items-center justify-between mt-4 pt-4 border-t border-gray-200 dark:border-slate-700">
<div class="text-xs text-gray-500 dark:text-slate-400"><i class="far fa-clock mr-1"></i>Last checked: Today 11:00</div>
<div class="flex gap-2">
<button class="inline-flex items-center px-2 py-1 bg-green-600 text-white text-xs rounded hover:bg-green-700 transition-colors font-medium"><i class="fas fa-sync-alt mr-1" style="font-size: 9px;"></i>Check Now</button>
<button class="inline-flex items-center px-2 py-1 bg-red-600 text-white text-xs rounded hover:bg-red-700 transition-colors font-medium"><i class="fas fa-trash mr-1" style="font-size: 9px;"></i>Remove</button>
</div>
</div>
</div>
</div>
</div>
<!-- Pagination Controls -->
<div class="mt-4 flex flex-col sm:flex-row items-center justify-between gap-4">
<div class="text-sm text-gray-600 dark:text-slate-400">Page <span class="font-semibold text-gray-900 dark:text-white">1</span> of <span class="font-semibold text-gray-900 dark:text-white">1</span></div>
<div class="flex items-center gap-1">
<button disabled class="px-3 py-2 text-sm border border-gray-300 dark:border-slate-600 rounded-lg bg-gray-100 dark:bg-slate-700 text-gray-400 dark:text-slate-500 cursor-not-allowed"><i class="fas fa-angle-double-left"></i></button>
<button disabled class="px-3 py-2 text-sm border border-gray-300 dark:border-slate-600 rounded-lg bg-gray-100 dark:bg-slate-700 text-gray-400 dark:text-slate-500 cursor-not-allowed"><i class="fas fa-angle-left"></i> Previous</button>
<span class="px-3 py-2 text-sm bg-primary text-white rounded-lg font-semibold">1</span>
<button disabled class="px-3 py-2 text-sm border border-gray-300 dark:border-slate-600 rounded-lg bg-gray-100 dark:bg-slate-700 text-gray-400 dark:text-slate-500 cursor-not-allowed">Next <i class="fas fa-angle-right"></i></button>
<button disabled class="px-3 py-2 text-sm border border-gray-300 dark:border-slate-600 rounded-lg bg-gray-100 dark:bg-slate-700 text-gray-400 dark:text-slate-500 cursor-not-allowed"><i class="fas fa-angle-double-right"></i></button>
</div>
</div>
<script>
function updateSSLBulkActions() {
const checkboxes = document.querySelectorAll('.ssl-checkbox:checked');
const bulkActions = document.getElementById('ssl-bulk-actions');
const selectedCount = document.getElementById('ssl-selected-count');
if (checkboxes.length > 0) {
bulkActions.classList.remove('hidden');
selectedCount.textContent = `${checkboxes.length} certificate(s) selected`;
} else {
bulkActions.classList.add('hidden');
}
}
function clearSSLSelection() {
document.querySelectorAll('.ssl-checkbox').forEach(cb => cb.checked = false);
updateSSLBulkActions();
}
function getSelectedSSLIds() {
return Array.from(document.querySelectorAll('.ssl-checkbox:checked')).map(cb => cb.value);
}
function bulkCheckSSL() {
const ids = getSelectedSSLIds();
console.log('Checking SSL certificates:', ids);
}
function bulkDeleteSSL() {
const ids = getSelectedSSLIds();
if (confirm(`Delete ${ids.length} certificate(s)? This action cannot be undone.`)) {
console.log('Deleting SSL certificates:', ids);
}
}
function checkAllCertificates() {
console.log('Checking all certificates...');
}
document.getElementById('ssl-search')?.addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
document.querySelectorAll('.ssl-cert-item').forEach(item => {
const text = item.textContent.toLowerCase();
item.style.display = text.includes(searchTerm) ? '' : 'none';
});
});
document.getElementById('ssl-filter')?.addEventListener('change', function(e) {
const filter = e.target.value;
document.querySelectorAll('.ssl-cert-item').forEach(item => {
if (filter === 'all') {
item.style.display = '';
} else {
const status = item.dataset.status;
item.style.display = status === filter ? '' : 'none';
}
});
});
</script>

View File

@@ -0,0 +1,241 @@
<!-- WHOIS TAB CONTENT -->
{% if not domain.is_active %}
<!-- Active Monitoring Disabled Banner -->
<div class="mb-4 bg-amber-50 dark:bg-amber-500/10 border border-amber-200 dark:border-amber-500/30 rounded-lg p-4">
<div class="flex items-start">
<i class="fas fa-pause-circle text-amber-500 dark:text-amber-400 mt-0.5 mr-3" style="font-size: 18px;"></i>
<div>
<h3 class="text-sm font-semibold text-amber-800 dark:text-amber-300">Active monitoring is disabled</h3>
<p class="text-xs text-amber-700 dark:text-amber-400 mt-1">This domain is not checked by the cron. You will not receive status or expiration alerts.</p>
<a href="/domains/{{ domain.id }}/edit#dns-monitoring" class="inline-flex items-center mt-2 text-xs font-medium text-amber-700 dark:text-amber-300 hover:text-amber-900 dark:hover:text-amber-100">
<i class="fas fa-edit mr-1"></i>Enable active monitoring in Edit
</a>
</div>
</div>
</div>
{% endif %}
<!-- 3-Column Layout: Registration, Registrant, Dates -->
<div class="grid grid-cols-1 lg:grid-cols-3 gap-3">
<!-- Registration Information -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900 flex items-center justify-between">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
<i class="fas fa-building text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Registration
</h3>
<form method="POST" action="/domains/{{ domain.id }}/refresh-whois" class="inline" onsubmit="prepareReturnTo(event)">
{{ csrf_field()|raw }}
<input type="hidden" name="return_to" value="">
<button type="submit" class="text-xs px-2 py-1 bg-green-600 text-white rounded hover:bg-green-700 transition-colors">
<i class="fas fa-sync-alt mr-1" style="font-size: 9px;"></i>
Refresh WHOIS
</button>
</form>
</div>
<div class="p-4">
<div class="space-y-2.5 text-xs">
<div>
<label class="text-gray-500 dark:text-slate-400 font-medium block mb-0.5">Registrar</label>
<p class="text-gray-900 dark:text-white font-semibold">{{ domain.registrar ?? 'Unknown' }}</p>
</div>
<div>
<label class="text-gray-500 dark:text-slate-400 font-medium block mb-0.5">Registrar URL</label>
{% if domain.registrar_url is defined and domain.registrar_url %}
<a href="{{ domain.registrar_url }}" target="_blank" class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 flex items-center">
<i class="fas fa-external-link-alt mr-1" style="font-size: 9px;"></i>
Visit Registrar
</a>
{% else %}
<span class="text-gray-400 dark:text-slate-500">-</span>
{% endif %}
</div>
<div>
<label class="text-gray-500 dark:text-slate-400 font-medium block mb-0.5">IANA ID</label>
<p class="text-gray-900 dark:text-white">{{ whoisData.iana_id ?? '-' }}</p>
</div>
<div>
<label class="text-gray-500 dark:text-slate-400 font-medium block mb-0.5">Abuse Contact</label>
{% if domain.abuse_email %}
<a href="mailto:{{ domain.abuse_email }}" class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 block break-all">{{ domain.abuse_email }}</a>
{% else %}
<span class="text-gray-400 dark:text-slate-500">-</span>
{% endif %}
</div>
<div>
<label class="text-gray-500 dark:text-slate-400 font-medium block mb-0.5">WHOIS Server</label>
<p class="text-gray-900 dark:text-white font-mono break-all">{{ whoisData.whois_server ?? '-' }}</p>
</div>
</div>
</div>
</div>
<!-- Registrant Information -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
<i class="fas fa-user text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Registrant
</h3>
</div>
<div class="p-4">
<div class="space-y-2.5 text-xs">
<div>
<label class="text-gray-500 dark:text-slate-400 font-medium block mb-0.5">Name</label>
<p class="text-gray-900 dark:text-white font-medium">{{ whoisData.owner ?? '-' }}</p>
</div>
<div>
<label class="text-gray-500 dark:text-slate-400 font-medium block mb-0.5">Organization</label>
<p class="text-gray-900 dark:text-white">{{ whoisData.organization ?? '-' }}</p>
</div>
<div>
<label class="text-gray-500 dark:text-slate-400 font-medium block mb-0.5">Email</label>
{% if whoisData.email is defined and whoisData.email %}
<a href="mailto:{{ whoisData.email }}" class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 break-all">{{ whoisData.email }}</a>
{% else %}
<span class="text-gray-400 dark:text-slate-500">-</span>
{% endif %}
</div>
<div>
<label class="text-gray-500 dark:text-slate-400 font-medium block mb-0.5">Country</label>
<p class="text-gray-900 dark:text-white">{{ whoisData.country ?? '-' }}</p>
</div>
<div>
<label class="text-gray-500 dark:text-slate-400 font-medium block mb-0.5">Privacy Protection</label>
<span class="inline-flex items-center px-2 py-0.5 bg-green-100 dark:bg-green-500/10 text-green-800 dark:text-green-400 text-xs font-semibold rounded">
<i class="fas fa-shield-alt mr-1" style="font-size: 9px;"></i>
Enabled
</span>
</div>
</div>
</div>
</div>
<!-- Important Dates -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
<i class="fas fa-calendar-alt text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Important Dates
</h3>
</div>
<div class="p-4">
<div class="space-y-2">
<div class="flex items-center p-2 bg-green-50 dark:bg-green-500/10 rounded border border-green-200 dark:border-green-800">
<div class="w-7 h-7 bg-green-500 rounded flex items-center justify-center mr-2 flex-shrink-0">
<i class="fas fa-plus text-white text-xs"></i>
</div>
<div>
<p class="text-xs text-gray-600 dark:text-slate-400 font-medium">Created</p>
<p class="text-xs font-semibold text-gray-900 dark:text-white">{% if whoisData.creation_date is defined %}{{ whoisData.creation_date|date('M d, Y') }}{% else %}-{% endif %}</p>
</div>
</div>
<div class="flex items-center p-2 bg-blue-50 dark:bg-blue-500/10 rounded border border-blue-200 dark:border-blue-800">
<div class="w-7 h-7 bg-blue-500 rounded flex items-center justify-center mr-2 flex-shrink-0">
<i class="fas fa-edit text-white text-xs"></i>
</div>
<div>
<p class="text-xs text-gray-600 dark:text-slate-400 font-medium">Last Updated</p>
<p class="text-xs font-semibold text-gray-900 dark:text-white">{% if domain.updated_date is defined and domain.updated_date %}{{ domain.updated_date|date('M d, Y') }}{% else %}-{% endif %}</p>
</div>
</div>
<div class="flex items-center p-2 bg-orange-50 dark:bg-orange-500/10 rounded border border-orange-200 dark:border-orange-800">
<div class="w-7 h-7 bg-orange-500 rounded flex items-center justify-center mr-2 flex-shrink-0">
<i class="fas fa-calendar-times text-white text-xs"></i>
</div>
<div>
<p class="text-xs text-gray-600 dark:text-slate-400 font-medium">Expires</p>
<p class="text-xs font-semibold text-orange-700 dark:text-orange-400">
{% if domain.expiration_date is defined and domain.expiration_date %}
{{ domain.expiration_date|date('M d, Y') }}{% if domain.daysLeft is defined and domain.daysLeft is not null %} ({{ domain.daysLeft }} days){% endif %}
{% else %}-{% endif %}
</p>
</div>
</div>
<div class="flex items-center p-2 bg-indigo-50 dark:bg-indigo-500/10 rounded border border-indigo-200 dark:border-indigo-800">
<div class="w-7 h-7 bg-indigo-500 rounded flex items-center justify-center mr-2 flex-shrink-0">
<i class="fas fa-sync text-white text-xs"></i>
</div>
<div>
<p class="text-xs text-gray-600 dark:text-slate-400 font-medium">Last Checked</p>
<p class="text-xs font-semibold text-gray-900 dark:text-white">{% if domain.last_checked is defined and domain.last_checked %}{{ domain.last_checked|date('M d, Y H:i') }}{% else %}-{% endif %}</p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Nameservers & Domain Status (2-col) -->
<div class="grid grid-cols-1 lg:grid-cols-2 gap-3 mt-3">
<!-- Nameservers -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
<i class="fas fa-server text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Name Servers
{% if whoisData.nameservers is defined and whoisData.nameservers is not empty %}
<span class="ml-1.5 px-1.5 py-0.5 bg-blue-100 dark:bg-blue-500/10 text-blue-800 dark:text-blue-400 text-xs font-semibold rounded">{{ whoisData.nameservers|length }}</span>
{% endif %}
</h3>
</div>
<div class="p-4">
<div class="space-y-1.5">
{% if whoisData.nameservers is defined and whoisData.nameservers is not empty %}
{% for ns in whoisData.nameservers %}
<div class="flex items-center p-2 bg-gray-50 dark:bg-slate-700 rounded hover:bg-gray-100 dark:hover:bg-slate-600 transition-colors">
<div class="w-5 h-5 bg-primary rounded flex items-center justify-center text-white font-bold text-xs mr-2 flex-shrink-0">{{ loop.index }}</div>
<span class="font-mono text-xs text-gray-900 dark:text-slate-200 break-all">{{ ns }}</span>
</div>
{% endfor %}
{% else %}
<div class="text-center py-3">
<i class="fas fa-server text-gray-300 dark:text-slate-600 text-lg mb-1"></i>
<p class="text-xs text-gray-500 dark:text-slate-400">No nameservers</p>
</div>
{% endif %}
</div>
</div>
</div>
<!-- Domain Status Codes -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden">
<div class="px-4 py-2 border-b border-gray-200 dark:border-slate-700 bg-gray-50 dark:bg-slate-900">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 uppercase tracking-wider flex items-center">
<i class="fas fa-shield-alt text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Domain Status Codes
{% if domain is defined and domain.parsedStatuses is defined and domain.parsedStatuses is not empty %}
<span class="ml-1.5 px-1.5 py-0.5 bg-blue-100 dark:bg-blue-500/10 text-blue-800 dark:text-blue-400 text-xs font-semibold rounded">{{ domain.parsedStatuses|length }}</span>
{% endif %}
</h3>
</div>
<div class="p-4">
<div class="flex flex-wrap gap-1.5">
{% if domain is defined and domain.parsedStatuses is defined and domain.parsedStatuses is not empty %}
{% for status in domain.parsedStatuses %}
<span class="px-2 py-1 bg-blue-100 dark:bg-blue-500/10 text-blue-800 dark:text-blue-400 rounded text-xs font-medium" title="{{ status }}">{{ status|replace({'_':' '})|title }}</span>
{% endfor %}
{% else %}
<span class="px-2 py-1 bg-gray-100 dark:bg-slate-700 text-gray-700 dark:text-slate-300 rounded text-xs font-medium">No status codes</span>
{% endif %}
</div>
</div>
</div>
</div>
<!-- Raw WHOIS (Collapsible) -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden mt-3">
<button onclick="toggleRawWhois()" class="w-full px-4 py-2 bg-gray-50 dark:bg-slate-900 text-left hover:bg-gray-100 dark:hover:bg-slate-700 transition-colors">
<h3 class="text-xs font-semibold text-gray-700 dark:text-slate-300 flex items-center justify-between">
<span class="flex items-center uppercase tracking-wider">
<i class="fas fa-code text-gray-400 dark:text-slate-500 mr-2" style="font-size: 10px;"></i>
Raw WHOIS Data
</span>
<i class="fas fa-chevron-down text-gray-400 dark:text-slate-500 transition-transform text-xs" id="raw-whois-icon"></i>
</h3>
</button>
<div id="raw-whois-content" class="hidden p-4 bg-gray-900 max-h-64 overflow-y-auto">
<pre class="text-xs text-green-400 font-mono">{{ whoisData|json_encode(constant('JSON_PRETTY_PRINT')) }}</pre>
</div>
</div>

View File

@@ -0,0 +1,269 @@
{% extends "layout/base.twig" %}
{% set title = 'Domain Details' %}
{% set pageTitle = domain.domain_name|default('Domain Details') %}
{% set pageDescription = 'Domain information and monitoring status' %}
{% set pageIcon = 'fas fa-globe' %}
{% set daysLeft = domain.daysLeft %}
{% set domainStatus = domain.displayStatus %}
{% set expiryColor = domain.expiryColor %}
{% block content %}
<!-- Top Action Bar -->
<div class="mb-3 flex flex-wrap gap-2 justify-between items-center">
<div class="flex flex-wrap gap-2">
{% if domain %}
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold {{ domain.statusClass }}">
<i class="fas {{ domain.statusIcon }} mr-1.5"></i>
{{ domain.statusText }}
</span>
{% if domain.displayStatus != 'available' %}
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold bg-{{ domain.expiryColor }}-100 dark:bg-{{ domain.expiryColor }}-500/10 text-{{ domain.expiryColor }}-800 dark:text-{{ domain.expiryColor }}-400 border border-{{ domain.expiryColor }}-200 dark:border-{{ domain.expiryColor }}-800">
<i class="fas fa-calendar-alt mr-1.5"></i>
{{ domain.daysLeft is not null ? domain.daysLeft ~ ' days left' : 'No expiry date' }}
</span>
{% endif %}
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold bg-indigo-100 dark:bg-indigo-500/10 text-indigo-800 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-800">
<i class="fas fa-{{ domain.is_active ? 'check-circle' : 'pause-circle' }} mr-1.5"></i>
{{ domain.is_active ? 'Monitoring Active' : 'Monitoring Paused' }}
</span>
{# Tags Display (match view.twig) #}
{% if domain.tags is not empty %}
{% set tags = domain.tags|split(',') %}
{% set tagColors = domain.tag_colors is not empty ? domain.tag_colors|split('|') : [] %}
{% set tagColorMap = {} %}
{% for availableTag in availableTags %}
{% set tagColorMap = tagColorMap|merge({(availableTag.name): availableTag.color}) %}
{% endfor %}
{% for tag in tags %}
{% set tag = tag|trim %}
{% set colorClass = tagColorMap[tag] ?? (tagColors[loop.index0] ?? 'bg-gray-100 dark:bg-slate-700 text-gray-700 dark:text-slate-300 border-gray-200 dark:border-slate-700') %}
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold border {{ colorClass }}">
<i class="fas fa-tag mr-1.5" style="font-size: 10px;"></i>
{{ tag|capitalize }}
</span>
{% endfor %}
{% endif %}
{% else %}
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold bg-green-100 dark:bg-green-500/10 text-green-800 dark:text-green-400 border border-green-200 dark:border-green-800">
<i class="fas fa-check-circle mr-1.5"></i>
Active
</span>
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold bg-orange-100 dark:bg-orange-500/10 text-orange-800 dark:text-orange-400 border border-orange-200 dark:border-orange-800">
<i class="fas fa-calendar-alt mr-1.5"></i>
65 days left
</span>
<span class="inline-flex items-center px-3 py-1.5 rounded-lg text-xs font-semibold bg-indigo-100 dark:bg-indigo-500/10 text-indigo-800 dark:text-indigo-400 border border-indigo-200 dark:border-indigo-800">
<i class="fas fa-check-circle mr-1.5"></i>
Monitoring Active
</span>
{% endif %}
</div>
<div class="flex gap-2 items-center">
{% if domain %}
<form method="POST" action="/domains/{{ domain.id }}/refresh-all" class="inline" onsubmit="prepareReturnTo(event); return handleRefreshBtn(this);">
{{ csrf_field()|raw }}
<input type="hidden" name="return_to" id="return_to_input">
<button type="submit" class="refresh-all-btn 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>
<span class="btn-label">Refresh All</span>
</button>
</form>
<a href="/domains/{{ domain.id }}/edit?from=/domains/{{ domain.id }}" 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">
{{ csrf_field()|raw }}
<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>
{% else %}
<button 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]" disabled>
<i class="fas fa-sync-alt mr-1.5"></i>
Refresh All
</button>
<a href="#" 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>
<button 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>
{% endif %}
<a href="/domains" class="inline-flex items-center justify-center px-3 py-2 border border-gray-300 dark:border-slate-600 text-gray-700 dark:text-slate-300 text-xs rounded-lg hover:bg-gray-50 dark:hover:bg-slate-700 transition-colors font-medium min-w-[80px] h-[32px]">
<i class="fas fa-arrow-left mr-1.5"></i>
Back
</a>
</div>
</div>
<!-- Tab Navigation -->
<div class="bg-white dark:bg-slate-800 rounded-lg border border-gray-200 dark:border-slate-700 overflow-hidden mb-3">
<div class="border-b border-gray-200 dark:border-slate-700">
<nav class="-mb-px flex">
<button onclick="switchTab('overview')" id="tab-overview" class="tab-button active flex-1 px-4 py-2.5 text-xs font-medium border-b-2 border-primary text-primary bg-blue-50 dark:bg-slate-700">
<i class="fas fa-chart-line mr-1.5" style="font-size: 10px;"></i>
Overview
</button>
<button onclick="switchTab('whois')" id="tab-whois" class="tab-button flex-1 px-4 py-2.5 text-xs font-medium border-b-2 border-transparent text-gray-500 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-200 hover:border-gray-300 dark:hover:border-slate-500">
<i class="fas fa-file-alt mr-1.5" style="font-size: 10px;"></i>
WHOIS
{% if not domain.is_active %}
<i class="fas fa-pause-circle text-amber-500 dark:text-amber-400 ml-1" style="font-size: 10px;" title="Active monitoring disabled"></i>
{% endif %}
</button>
<button onclick="switchTab('ssl')" id="tab-ssl" class="tab-button flex-1 px-4 py-2.5 text-xs font-medium border-b-2 border-transparent text-gray-500 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-200 hover:border-gray-300 dark:hover:border-slate-500">
<i class="fas fa-lock mr-1.5" style="font-size: 10px;"></i>
SSL
<span class="ml-1.5 px-1.5 py-0.5 bg-green-100 dark:bg-green-500/10 text-green-800 dark:text-green-400 text-xs font-semibold rounded">2</span>
<span class="ml-1 px-1.5 py-0.5 bg-red-100 dark:bg-red-500/10 text-red-800 dark:text-red-400 text-xs font-semibold rounded">1</span>
</button>
<button onclick="switchTab('dns')" id="tab-dns" class="tab-button flex-1 px-4 py-2.5 text-xs font-medium border-b-2 border-transparent text-gray-500 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-200 hover:border-gray-300 dark:hover:border-slate-500">
<i class="fas fa-network-wired mr-1.5" style="font-size: 10px;"></i>
DNS
{% if not (domain.dns_monitoring_enabled|default(1)) %}
<i class="fas fa-pause-circle text-amber-500 dark:text-amber-400 ml-1" style="font-size: 10px;" title="DNS monitoring disabled"></i>
{% else %}
{% if dnsRecordCount|default(0) > 0 %}
<span class="ml-1.5 px-1.5 py-0.5 bg-blue-100 dark:bg-blue-500/10 text-blue-800 dark:text-blue-400 text-xs font-semibold rounded">{{ dnsRecordCount }}</span>
{% endif %}
{% if dnsHasCloudflare|default(false) %}
<i class="fas fa-cloud text-orange-500 dark:text-orange-400 ml-1" style="font-size: 10px;" title="Behind Cloudflare"></i>
{% endif %}
{% endif %}
</button>
<button onclick="switchTab('billing')" id="tab-billing" class="tab-button flex-1 px-4 py-2.5 text-xs font-medium border-b-2 border-transparent text-gray-500 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-200 hover:border-gray-300 dark:hover:border-slate-500">
<i class="fas fa-dollar-sign mr-1.5" style="font-size: 10px;"></i>
Billing
</button>
<button onclick="switchTab('notifications')" id="tab-notifications" class="tab-button flex-1 px-4 py-2.5 text-xs font-medium border-b-2 border-transparent text-gray-500 dark:text-slate-400 hover:text-gray-700 dark:hover:text-slate-200 hover:border-gray-300 dark:hover:border-slate-500">
<i class="fas fa-bell mr-1.5" style="font-size: 10px;"></i>
Notifications
{% if logs is defined and logs|length > 0 %}
<span class="ml-1.5 px-1.5 py-0.5 bg-blue-100 dark:bg-blue-500/10 text-blue-800 dark:text-blue-400 text-xs font-semibold rounded">{{ logs|length }}</span>
{% endif %}
</button>
</nav>
</div>
</div>
<!-- Tab Content -->
<div class="tab-content-wrapper">
<div id="content-overview" class="tab-content">
{% include 'domains/tabs/overview.twig' %}
</div>
<div id="content-whois" class="tab-content hidden">
{% include 'domains/tabs/whois.twig' %}
</div>
<div id="content-ssl" class="tab-content hidden">
{% include 'domains/tabs/ssl.twig' %}
</div>
<div id="content-dns" class="tab-content hidden">
{% include 'domains/tabs/dns.twig' %}
</div>
<div id="content-billing" class="tab-content hidden">
{% include 'domains/tabs/billing.twig' %}
</div>
<div id="content-notifications" class="tab-content hidden">
{% include 'domains/tabs/notification.twig' %}
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
document.addEventListener('DOMContentLoaded', function() {
const hash = (window.location.hash || '#overview').replace('#','');
switchTab(hash);
});
function updateTabHash(tabName) {
const url = new URL(window.location.href);
url.hash = '#' + tabName;
window.history.replaceState({}, '', url);
}
function switchTab(tabName) {
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.add('hidden');
});
document.querySelectorAll('.tab-button').forEach(button => {
button.classList.remove('active', 'border-primary', 'text-primary', 'bg-blue-50', 'dark:bg-slate-700');
button.classList.add('border-transparent', 'text-gray-500', 'dark:text-slate-400');
});
document.getElementById('content-' + tabName).classList.remove('hidden');
const activeTab = document.getElementById('tab-' + tabName);
activeTab.classList.add('active', 'border-primary', 'text-primary', 'bg-blue-50', 'dark:bg-slate-700');
activeTab.classList.remove('border-transparent', 'text-gray-500', 'dark:text-slate-400');
updateTabHash(tabName);
}
function toggleRawWhois() {
const content = document.getElementById('raw-whois-content');
const icon = document.getElementById('raw-whois-icon');
content.classList.toggle('hidden');
icon.classList.toggle('rotate-180');
}
function toggleDnsHistory() {
const content = document.getElementById('dns-history-content');
const icon = document.getElementById('dns-history-icon');
content.classList.toggle('hidden');
icon.classList.toggle('rotate-180');
}
function toggleNotesEdit(editMode) {
const viewMode = document.getElementById('notes-view-mode');
const editModeEl = document.getElementById('notes-edit-mode');
const editBtn = document.getElementById('notes-edit-btn');
if (editMode) {
viewMode.classList.add('hidden');
editModeEl.classList.remove('hidden');
editBtn.classList.add('hidden');
document.getElementById('overview-notes-textarea').focus();
} else {
viewMode.classList.remove('hidden');
editModeEl.classList.add('hidden');
editBtn.classList.remove('hidden');
document.getElementById('overview-notes-textarea').value = {{ domain.notes|default('')|json_encode|raw }};
}
}
function prepareReturnTo(e) {
const form = e.target;
const input = form.querySelector('input[name="return_to"]') || document.getElementById('return_to_input');
if (!input) return;
const url = new URL(window.location.href);
if (!url.hash) {
const active = document.querySelector('.tab-button.active');
if (active && active.id) {
url.hash = '#' + active.id.replace('tab-','');
}
}
input.value = url.pathname + (url.hash ? url.hash : '');
}
function handleRefreshBtn(form) {
var btn = form.querySelector('.refresh-all-btn');
if (!btn || btn.disabled) return false;
btn.disabled = true;
btn.classList.remove('bg-green-600', 'hover:bg-green-700');
btn.classList.add('bg-gray-400', 'cursor-not-allowed');
var icon = btn.querySelector('i');
var label = btn.querySelector('.btn-label');
if (icon) icon.classList.add('fa-spin');
if (label) label.textContent = 'Refreshing...';
return true;
}
</script>
{% endblock %}

View File

@@ -50,11 +50,11 @@
{% endfor %}
</div>
<div class="flex gap-2 items-center">
<form method="POST" action="/domains/{{ domain.id }}/refresh" class="inline">
<form method="POST" action="/domains/{{ domain.id }}/refresh-whois" class="inline">
{{ csrf_field() }}
<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
Refresh WHOIS
</button>
</form>
<a href="/domains/{{ domain.id }}/edit?from=/domains/{{ domain.id }}" 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]">