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:
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
279
app/Views/domains/tabs/billing.twig
Normal file
279
app/Views/domains/tabs/billing.twig
Normal 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>
|
||||
507
app/Views/domains/tabs/dns.twig
Normal file
507
app/Views/domains/tabs/dns.twig
Normal 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>
|
||||
131
app/Views/domains/tabs/notification.twig
Normal file
131
app/Views/domains/tabs/notification.twig
Normal 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 %}
|
||||
284
app/Views/domains/tabs/overview.twig
Normal file
284
app/Views/domains/tabs/overview.twig
Normal 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 — 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>
|
||||
379
app/Views/domains/tabs/ssl.twig
Normal file
379
app/Views/domains/tabs/ssl.twig
Normal 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>
|
||||
241
app/Views/domains/tabs/whois.twig
Normal file
241
app/Views/domains/tabs/whois.twig
Normal 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>
|
||||
269
app/Views/domains/view-detailed.twig
Normal file
269
app/Views/domains/view-detailed.twig
Normal 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 %}
|
||||
@@ -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]">
|
||||
|
||||
Reference in New Issue
Block a user