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.
2026-03-08 14:32:05 +02:00
<!-- 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 %}
Improve security, validation, and isolation checks
Add multiple security and validation improvements across the app:
- Prevent session fixation: regenerate session ID on login and after successful 2FA; tighten session cookie params (Secure, HttpOnly, SameSite=Lax).
- Harden installer: add CSRF checks for install/update flows and use PDO::quote when injecting admin credentials into SQL migration to avoid injection; add csrf_field() to installer templates.
- Template hardening: add safe_url and safe_mailto Twig filters, escape tag names for JS, and add rel="noopener noreferrer" to external links to mitigate XSS/opener risks.
- Domain controller: validate referrer to avoid open redirects, enforce user isolation mode when finding/deleting/updating domains and when assigning notification groups (ensures users only affect their own resources).
- Notification groups: verify channel belongs to group before deleting or toggling to prevent unauthorized access.
- ErrorLog: whitelist allowed sort columns to avoid arbitrary column injection in ORDER BY.
- Routes: move the debug whois route to protected/admin area.
These changes collectively reduce attack surface (XSS, open redirect, session fixation, SQL injection) and enforce proper resource isolation and input validation.
2026-03-11 00:03:54 +02:00
<a href=" {{ domain .registrar_url | safe_url }} " target="_blank" rel="noopener noreferrer" class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 flex items-center">
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.
2026-03-08 14:32:05 +02:00
<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 %}
Improve security, validation, and isolation checks
Add multiple security and validation improvements across the app:
- Prevent session fixation: regenerate session ID on login and after successful 2FA; tighten session cookie params (Secure, HttpOnly, SameSite=Lax).
- Harden installer: add CSRF checks for install/update flows and use PDO::quote when injecting admin credentials into SQL migration to avoid injection; add csrf_field() to installer templates.
- Template hardening: add safe_url and safe_mailto Twig filters, escape tag names for JS, and add rel="noopener noreferrer" to external links to mitigate XSS/opener risks.
- Domain controller: validate referrer to avoid open redirects, enforce user isolation mode when finding/deleting/updating domains and when assigning notification groups (ensures users only affect their own resources).
- Notification groups: verify channel belongs to group before deleting or toggling to prevent unauthorized access.
- ErrorLog: whitelist allowed sort columns to avoid arbitrary column injection in ORDER BY.
- Routes: move the debug whois route to protected/admin area.
These changes collectively reduce attack surface (XSS, open redirect, session fixation, SQL injection) and enforce proper resource isolation and input validation.
2026-03-11 00:03:54 +02:00
<a href=" {{ domain .abuse_email | safe_mailto }} " class="text-blue-600 dark:text-blue-400 hover:text-blue-800 dark:hover:text-blue-300 block break-all"> {{ domain .abuse_email }} </a>
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.
2026-03-08 14:32:05 +02:00
{% 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>