Add DNS monitoring and refresh functionality

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

View File

@@ -259,6 +259,24 @@ class Domain extends Model
return $stats;
}
/**
* Get effective status for sorting (active + daysLeft within threshold = expiring_soon)
*/
private static function getEffectiveStatusForSort(array $domain, int $expiringThreshold): string
{
$status = $domain['status'] ?? '';
if ($status === 'inactive') {
return 'inactive';
}
if ($status === 'active' && !empty($domain['expiration_date'])) {
$daysLeft = (int) floor((strtotime($domain['expiration_date']) - time()) / 86400);
if ($daysLeft <= $expiringThreshold && $daysLeft >= 0) {
return 'expiring_soon';
}
}
return $status ?: 'error';
}
/**
* Get filtered, sorted, and paginated domains
*/
@@ -332,10 +350,33 @@ class Domain extends Model
$totalDomains = count($domains);
// Apply sorting
usort($domains, function($a, $b) use ($sortBy, $sortOrder) {
usort($domains, function($a, $b) use ($sortBy, $sortOrder, $expiringThreshold) {
$aVal = $a[$sortBy] ?? '';
$bVal = $b[$sortBy] ?? '';
// When sorting by status: use effective status (active + daysLeft<=threshold = expiring_soon) and logical priority order
if ($sortBy === 'status') {
$aVal = self::getEffectiveStatusForSort($a, $expiringThreshold);
$bVal = self::getEffectiveStatusForSort($b, $expiringThreshold);
$priority = [
'expired' => 1,
'redemption_period' => 2,
'pending_delete' => 3,
'expiring_soon' => 4,
'active' => 5,
'available' => 6,
'error' => 7,
'inactive' => 8,
];
$aOrder = $priority[$aVal] ?? 99;
$bOrder = $priority[$bVal] ?? 99;
$comparison = $aOrder <=> $bOrder;
if ($comparison === 0) {
$comparison = strcasecmp($a['domain_name'] ?? '', $b['domain_name'] ?? '');
}
return $sortOrder === 'desc' ? -$comparison : $comparison;
}
$comparison = strcasecmp($aVal, $bVal);
return $sortOrder === 'desc' ? -$comparison : $comparison;
});