mailwolt/app/Livewire/Ui/Domain/Modal/DomainDnsModal.php

1278 lines
51 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

<?php
namespace App\Livewire\Ui\Domain\Modal;
use App\Models\Domain;
use App\Models\TlsaRecord;
use App\Support\NetProbe;
use Illuminate\Support\Facades\DB;
use Livewire\Attributes\On;
use LivewireUI\Modal\ModalComponent;
class DomainDnsModal extends ModalComponent
{
public int $domainId;
public string $domainName = '';
public string $zone = '';
public string $ttl = '3600';
public array $recordColors = [];
/** @var array<int,array<string,string|int|null>> */
public array $static = [];
/** @var array<int,array<string,string|int|null>> */
public array $dynamic = [];
/** @var array<int,array<string,string|int|null>> */
public array $optional = [];
/** Farbklassen für den Status */
public array $stateColors = [
'ok' => 'border-emerald-400/30 bg-emerald-500/10',
'missing' => 'border-rose-400/30 bg-rose-500/10',
'syntax' => 'border-amber-400/30 bg-amber-500/10',
'neutral' => 'border-white/10 bg-white/5',
];
/** erst nach Klick färben */
public bool $checked = false;
public static function modalMaxWidth(): string { return '6xl'; }
public function mount(int $domainId): void
{
$this->recordColors = [
'A' => 'bg-cyan-500/20 text-cyan-300',
'AAAA' => 'bg-blue-500/20 text-blue-300',
'MX' => 'bg-emerald-500/20 text-emerald-300',
'CNAME' => 'bg-indigo-500/20 text-indigo-300',
'PTR' => 'bg-amber-500/20 text-amber-300',
'TXT' => 'bg-violet-500/20 text-violet-300',
'SRV' => 'bg-rose-500/20 text-rose-300',
'TLSA' => 'bg-red-500/20 text-red-300',
'OPTIONAL' => 'bg-gray-500/20 text-gray-300',
];
$d = Domain::findOrFail($domainId);
$this->domainId = $domainId;
$this->domainName = $d->domain;
$tlsa = TlsaRecord::forServer()->where('service', '_25._tcp')->latest('id')->first();
$ips = NetProbe::resolve();
$ipv4 = $ips['ipv4'];
$ipv6 = $ips['ipv6'];
$this->zone = $this->extractZone($d->domain);
$mta_sub = env('MTA_SUB');
$base = env('BASE_DOMAIN');
$mailServerFqdn = "{$mta_sub}.{$base}";
// --- Infrastruktur (global) ---
$this->static = [
['type' => 'A', 'name' => $mailServerFqdn, 'value' => $ipv4],
['type' => 'PTR', 'name' => $this->ptrFromIPv4($ipv4), 'value' => $mailServerFqdn],
];
if ($ipv6) {
$this->static[] = ['type' => 'AAAA','name' => $mailServerFqdn, 'value' => $ipv6];
$this->static[] = ['type' => 'PTR', 'name' => $this->ptrFromIPv6($ipv6), 'value' => $mailServerFqdn];
}
if ($tlsa?->dns_string) {
$this->static[] = [
'type' => 'TLSA',
'name' => "{$tlsa->service}.{$tlsa->host}",
'value' => "{$tlsa->usage} {$tlsa->selector} {$tlsa->matching} {$tlsa->hash}",
];
}
// --- Domain-spezifisch ---
$spf = "v=spf1 ip4:{$ipv4}" . ($ipv6 ? " ip6:{$ipv6}" : '') . " mx -all";
$dmarc = "v=DMARC1; p=none; rua=mailto:dmarc@{$this->domainName}; pct=100";
$dkim = DB::table('dkim_keys')->where('domain_id', $d->id)->where('is_active', 1)->orderByDesc('id')->first();
$selector = $dkim ? $dkim->selector : 'mwl1';
$dkimHost = "{$selector}._domainkey.{$this->domainName}";
$dkimTxt = $dkim && !str_starts_with(trim($dkim->public_key_txt), 'v=')
? 'v=DKIM1; k=rsa; p=' . trim($dkim->public_key_txt)
: (trim($dkim->public_key_txt ?? '') ?: 'v=DKIM1; k=rsa; p=');
$this->dynamic = [
['type' => 'MX', 'name' => $this->domainName, 'value' => "10 {$mailServerFqdn}."],
['type' => 'CNAME', 'name' => "autoconfig.{$this->domainName}", 'value' => "{$mailServerFqdn}."],
['type' => 'CNAME', 'name' => "autodiscover.{$this->domainName}", 'value' => "{$mailServerFqdn}."],
['type' => 'TXT', 'name' => $this->domainName, 'value' => $spf],
['type' => 'TXT', 'name' => "_dmarc.{$this->domainName}", 'value' => $dmarc],
['type' => 'TXT', 'name' => $dkimHost, 'value' => $dkimTxt],
];
// $this->optional = [
// ['type' => 'SRV', 'name' => "_autodiscover._tcp.{$this->domainName}", 'value' => "0 1 443 {$mailServerFqdn}."],
// ['type' => 'SRV', 'name' => "_imaps._tcp.{$this->domainName}", 'value' => "0 1 993 {$mailServerFqdn}."],
// ['type' => 'SRV', 'name' => "_pop3s._tcp.{$this->domainName}", 'value' => "0 1 995 {$mailServerFqdn}."],
// ['type' => 'SRV', 'name' => "_submission._tcp.{$this->domainName}", 'value' => "0 1 587 {$mailServerFqdn}."],
// ];
$this->optional = [
// --- Service-Erkennung ---
[
'type' => 'SRV',
'name' => "_autodiscover._tcp.{$this->domainName}",
'value' => "0 1 443 {$mailServerFqdn}.",
'helpLabel' => 'Autodiscover SRV',
'helpUrl' => 'https://learn.microsoft.com/en-us/exchange/architecture/client-access/autodiscover',
'info' => 'Ermöglicht automatische Mailkonfiguration (v. a. für Outlook und Apple Mail).',
],
[
'type' => 'SRV',
'name' => "_imaps._tcp.{$this->domainName}",
'value' => "0 1 993 {$mailServerFqdn}.",
'helpLabel' => 'IMAPS SRV',
'helpUrl' => 'https://datatracker.ietf.org/doc/html/rfc6186',
'info' => 'Definiert den IMAP-Dienst (Port 993, SSL/TLS). Empfohlen für Clients.',
],
[
'type' => 'SRV',
'name' => "_pop3s._tcp.{$this->domainName}",
'value' => "0 1 995 {$mailServerFqdn}.",
'helpLabel' => 'POP3S SRV',
'helpUrl' => 'https://datatracker.ietf.org/doc/html/rfc6186',
'info' => 'Optional für POP3 (Port 995, SSL/TLS). Nur falls POP3 unterstützt wird.',
],
[
'type' => 'SRV',
'name' => "_submission._tcp.{$this->domainName}",
'value' => "0 1 587 {$mailServerFqdn}.",
'helpLabel' => 'Submission SRV',
'helpUrl' => 'https://datatracker.ietf.org/doc/html/rfc6409',
'info' => 'Definiert den SMTP-Submission-Dienst (Port 587, STARTTLS).',
],
// --- Sicherheit & Policies ---
[
'type' => 'TXT',
'name' => "_mta-sts.{$this->domainName}",
'value' => "v=STSv1; id=20250101T000000Z;",
'helpLabel' => 'MTA-STS Policy',
'helpUrl' => 'https://datatracker.ietf.org/doc/html/rfc8461',
'info' => 'Sichert SMTP-Verbindungen durch verpflichtendes TLS (Mail Transport Security).',
],
[
'type' => 'CNAME',
'name' => "mta-sts.{$this->domainName}",
'value' => "{$mailServerFqdn}.",
'helpLabel' => 'MTA-STS Host',
'helpUrl' => 'https://datatracker.ietf.org/doc/html/rfc8461',
'info' => 'Zeigt auf den Server, der die MTA-STS-Policy unter /.well-known/mta-sts.txt bereitstellt.',
],
[
'type' => 'TXT',
'name' => "_smtp._tls.{$this->domainName}",
'value' => "v=TLSRPTv1; rua=mailto:tlsrpt@{$this->domainName}",
'helpLabel' => 'TLS-RPT (Reporting)',
'helpUrl' => 'https://datatracker.ietf.org/doc/html/rfc8460',
'info' => 'Ermöglicht Berichte zu fehlerhaften TLS-Verbindungen beim Mailversand (TLS Reporting).',
],
// --- Komfort / Web ---
[
'type' => 'CNAME',
'name' => env('WEBMAIL_SUB') . ".{$this->domainName}",
'value' => "{$mailServerFqdn}.",
'helpLabel' => 'Webmail Alias',
'helpUrl' => 'https://en.wikipedia.org/wiki/CNAME_record',
'info' => 'Erlaubt Zugriff auf Webmail unter webmail.domain.tld.',
],
];
// wichtig: initial neutral einfärben (kein Check gelaufen)
$this->applyNeutralColors();
}
private function applyNeutralColors(): void
{
foreach (['static', 'dynamic', 'optional'] as $group) {
$this->{$group} = array_map(fn($r) => array_merge($r, [
'state' => 'neutral',
'boxClass' => $this->stateColors['neutral'],
'actual' => '',
]), $this->{$group});
}
}
/** Klick im Footer löst diesen Check aus */
#[On('domain:check-dns')]
public function checkDns(): void
{
// dynamische (Pflicht) Records prüfen
foreach ($this->dynamic as $i => $r) {
$actual = $this->dig($r['type'], $r['name']);
$state = $this->stateFor($r['type'], $r['value'], $actual, optional:false);
$this->dynamic[$i]['actual'] = $actual;
$this->dynamic[$i]['state'] = $state;
$this->dynamic[$i]['boxClass'] = $this->stateColors[$state] ?? $this->stateColors['neutral'];
$this->dynamic[$i]['display_actual'] = $this->normActual($r['type'], $actual);
}
// statische (Pflicht) Records prüfen
foreach ($this->static as $i => $r) {
$actual = $this->dig($r['type'], $r['name']);
$state = $this->stateFor($r['type'], $r['value'], $actual, optional:false);
$this->static[$i]['actual'] = $actual;
$this->static[$i]['state'] = $state;
$this->static[$i]['boxClass'] = $this->stateColors[$state] ?? $this->stateColors['neutral'];
$this->static[$i]['display_actual'] = $this->normActual($r['type'], $actual);
}
// optionale Records: nie „missing“, nur neutral|syntax|ok
foreach ($this->optional as $i => $r) {
$actual = $this->dig($r['type'], $r['name']);
$state = $this->stateFor($r['type'], $r['value'], $actual, optional:true);
$this->optional[$i]['actual'] = $actual;
$this->optional[$i]['state'] = $state;
$this->optional[$i]['boxClass'] = $this->stateColors[$state] ?? $this->stateColors['neutral'];
$this->optional[$i]['display_actual'] = $this->normActual($r['type'], $actual);
}
$this->checked = true;
}
/** Prüft eine Record-Liste und gibt die Liste mit 'state' zurück */
private function checkGroup(array $group, bool $optional = false): array
{
foreach ($group as &$r) {
$type = (string)$r['type'];
$name = (string)$r['name'];
$exp = (string)$r['value'];
$act = $this->dig($type, $name);
if ($act === '') {
$r['state'] = $optional ? 'missing' : 'missing';
continue;
}
$ok = $this->compareDns($type, $exp, $act);
$r['state'] = $ok ? 'ok' : 'mismatch';
}
return $group;
}
/* ---------- DNS & Bewertung ---------- */
// private function dig(string $type, string $name): string
// {
// $type = strtoupper($type);
// $name = rtrim($name, '.'); // intern ohne trailing dot
// $out = @shell_exec('dig +timeout=2 +tries=1 +short '
// . escapeshellarg($name) . ' ' . escapeshellarg($type) . ' 2>/dev/null') ?? '';
// $out = trim($out);
//
// // Mehrzeiliges TXT zu einer Zeile squashen, Quotes weg
// if ($type === 'TXT' && $out !== '') {
// $lines = array_filter(array_map('trim', explode("\n", $out)));
// $joined = implode('', array_map(fn($l)=>trim($l,'"'), $lines));
// return $joined;
// }
//
// // Nur erste Zeile vergleichen
// if ($out !== '') {
// $out = trim(explode("\n", $out)[0]);
// // MX/SRV/CNAME Ziele ohne trailing dot vergleichen
// if (in_array($type, ['MX','CNAME','SRV'])) {
// $out = rtrim($out, '.');
// }
// }
// return $out;
// }
private function dig(string $type, string $name): string
{
$type = strtoupper($type);
$name = rtrim($name, '.');
$out = @shell_exec(
'dig +timeout=2 +tries=1 +short ' . escapeshellarg($name) . ' ' . escapeshellarg($type) . ' 2>/dev/null'
) ?? '';
$out = trim($out);
if ($out === '') return '';
// TXT: mehrere Zeilen / Quotes zu einer Zeile squashen
if ($type === 'TXT') {
$lines = array_filter(array_map('trim', explode("\n", $out)));
$joined = implode('', array_map(fn($l) => trim($l, '"'), $lines));
return $joined;
}
// TLSA: Kanonisieren (Leerzeichen/Zeilenumbrüche im Hash, Großbuchstaben, …)
if ($type === 'TLSA') {
// nimm die ganze Ausgabe (kann mehrzeilig sein)
return $this->canonicalizeTlsa(preg_replace('/\s+/', ' ', $out)) ?? '';
}
// Für alles andere: erste Zeile reicht, trailing dots weg wo sinnvoll
$line = trim(strtok($out, "\n"));
if (in_array($type, ['MX','CNAME','SRV'])) $line = rtrim($line, '.');
return $line;
}
// private function stateFor(string $type, string $expected, string $actual, bool $optional): string
// {
// if ($actual === '') {
// return $optional ? 'neutral' : 'missing';
// }
//
// $type = strtoupper($type);
// $exp = $this->normExpected($type, $expected);
// $act = $this->normActual($type, $actual);
//
// // Syntax plausibilisieren
// $syntaxOk = $this->validateSyntax($type, $act);
// if (!$syntaxOk) return 'syntax';
//
// // TXT-Policies: nur „Startet mit v=…“ prüfen → OK,
// // selbst wenn Inhalt nicht 1:1 dem Vorschlag entspricht.
// if ($type === 'TXT') {
// $upperExp = strtoupper($exp);
// $upperAct = strtoupper($act);
// if (str_starts_with($upperExp, 'V=SPF1')) return str_starts_with($upperAct, 'V=SPF1') ? 'ok' : 'syntax';
// if (str_starts_with($upperExp, 'V=DMARC1')) return str_starts_with($upperAct, 'V=DMARC1') ? 'ok' : 'syntax';
// if (str_starts_with($upperExp, 'V=DKIM1')) return str_starts_with($upperAct, 'V=DKIM1') ? 'ok' : 'syntax';
// return ($act !== '') ? 'ok' : ($optional ? 'neutral' : 'missing');
// }
//
// // MX: „prio host“ wir prüfen Host grob
// if ($type === 'MX') {
// $parts = preg_split('/\s+/', $act);
// $host = strtolower($parts[1] ?? $act);
// $expHost = strtolower(preg_replace('/^\d+\s+/', '', $exp));
// return ($host === $expHost) ? 'ok' : 'syntax';
// }
//
// // SRV: „prio weight port host“ Port + Host grob
// if ($type === 'SRV') {
// $ap = preg_split('/\s+/', $act);
// $ep = preg_split('/\s+/', $exp);
// if (count($ap) >= 4 && count($ep) >= 4) {
// $aport = (int)$ap[2];
// $eport = (int)$ep[2];
// $ahost = strtolower(rtrim(end($ap), '.'));
// $ehost = strtolower(rtrim(end($ep), '.'));
// return ($aport === $eport && $ahost === $ehost) ? 'ok' : 'syntax';
// }
// return 'syntax';
// }
//
// // CNAME/A/AAAA/PTR/TLSA: Gleichheit nach Normalisierung
// return ($act === $exp) ? 'ok' : 'syntax';
// }
// private function normExpected(string $type, string $v): string
// {
// $v = trim($v);
// $t = strtoupper($type);
// if (in_array($t, ['MX','CNAME','SRV'])) $v = rtrim($v, '.');
// if ($t === 'PTR') $v = strtolower(rtrim($v, '.'));
// if ($t === 'TLSA') $v = preg_replace('/\s+/', ' ', $v);
// return $v;
// }
// private function normActual(string $type, string $v): string
// {
// $v = trim($v);
// $t = strtoupper($type);
// if (in_array($t, ['MX','CNAME','SRV'])) $v = rtrim($v, '.');
// if ($t === 'PTR') $v = strtolower(rtrim($v, '.'));
// if ($t === 'TLSA') {
// $v = preg_replace('/\s+/', '', $v); // Hash-Zeilen zusammenfügen
// $v = preg_replace('/^([0-3][\s]+[01][\s]+[123])/', '$1 ', $v); // spacing nach Header erzwingen
// }
// return $v;
// }
private function stateFor(string $type, string $expected, string $actual, bool $optional): string
{
if ($actual === '') return $optional ? 'neutral' : 'missing';
$type = strtoupper($type);
$exp = $this->normExpected($type, $expected);
$act = $this->normActual($type, $actual);
// Syntaxcheck nach Normalisierung
if (!$this->validateSyntax($type, $act)) return 'syntax';
// TXT: nur „v=…“-Präfix grob prüfen
if ($type === 'TXT') {
$E = strtoupper($exp);
$A = strtoupper($act);
if (str_starts_with($E, 'V=SPF1')) return str_starts_with($A, 'V=SPF1') ? 'ok' : 'syntax';
if (str_starts_with($E, 'V=DMARC1')) return str_starts_with($A, 'V=DMARC1') ? 'ok' : 'syntax';
if (str_starts_with($E, 'V=DKIM1')) return str_starts_with($A, 'V=DKIM1') ? 'ok' : 'syntax';
return 'ok';
}
if ($type === 'MX') {
$parts = preg_split('/\s+/', $act);
$host = strtolower($parts[1] ?? $act);
$expHost = strtolower(preg_replace('/^\d+\s+/', '', $exp));
return ($host === $expHost) ? 'ok' : 'syntax';
}
if ($type === 'SRV') {
$ap = preg_split('/\s+/', $act);
$ep = preg_split('/\s+/', $exp);
if (count($ap) >= 4 && count($ep) >= 4) {
return ((int)$ap[2] === (int)$ep[2] && strtolower(end($ap)) === strtolower(end($ep))) ? 'ok' : 'syntax';
}
return 'syntax';
}
// TLSA / A / AAAA / CNAME / PTR: exakter Vergleich nach Norm.
return ($act === $exp) ? 'ok' : 'syntax';
}
private function canonicalizeTlsa(?string $v): ?string
{
if (!$v) return null;
$v = trim($v);
// tokenisiere: u s m [hash...]
$parts = preg_split('/\s+/', $v);
if (count($parts) < 4) return null;
$u = $parts[0]; $s = $parts[1]; $m = $parts[2];
// restliche Teile gehören zum Hash zusammenfügen, Non-hex entfernen, kleinschreiben
$hash = strtolower(preg_replace('/[^0-9a-f]/i', '', implode('', array_slice($parts, 3))));
if ($hash === '') return null;
return sprintf('%s %s %s %s', $u, $s, $m, $hash);
}
private function normExpected(string $type, string $v): string
{
$v = trim($v);
$t = strtoupper($type);
if (in_array($t, ['MX','CNAME','SRV'])) $v = rtrim($v, '.');
if ($t === 'PTR') $v = strtolower(rtrim($v, '.'));
if ($t === 'TLSA') $v = $this->canonicalizeTlsa($v) ?? $v;
return $v;
}
private function normActual(string $type, string $v): string
{
$v = trim($v);
$t = strtoupper($type);
if (in_array($t, ['MX','CNAME','SRV'])) $v = rtrim($v, '.');
if ($t === 'PTR') $v = strtolower(rtrim($v, '.'));
if ($t === 'TLSA') $v = $this->canonicalizeTlsa($v) ?? $v;
return $v;
}
// private function validateSyntax(string $type, string $val): bool
// {
// $t = strtoupper($type);
// if ($val === '') return false;
//
// return match ($t) {
// 'A' => (bool)filter_var($val, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4),
// 'AAAA' => (bool)filter_var($val, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6),
// 'CNAME' => (bool)preg_match('/^[a-z0-9._-]+$/i', $val),
// 'PTR' => (bool)preg_match('/\.(in-addr|ip6)\.arpa$/i', $val),
// 'MX' => (bool)preg_match('/^\d+\s+[a-z0-9._-]+$/i', $val),
// 'SRV' => (bool)preg_match('/^\d+\s+\d+\s+\d+\s+[a-z0-9._-]+$/i', $val),
// 'TLSA' => (bool)preg_match('/^[0-3]\s+[01]\s+[123]\s+[0-9a-f\s]{32,}$/i', $val),
// 'TXT' => strlen($val) > 0,
// default => true,
// };
// }
private function validateSyntax(string $type, string $val): bool
{
$t = strtoupper($type);
if ($val === '') return false;
if ($t === 'TLSA') {
$canon = $this->canonicalizeTlsa($val);
return is_string($canon) && (bool)preg_match('/^[0-3]\s+[01]\s+[123]\s+[0-9a-f]{32,}$/', $canon);
}
return match ($t) {
'A' => (bool)filter_var($val, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4),
'AAAA' => (bool)filter_var($val, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6),
'CNAME' => (bool)preg_match('/^[a-z0-9._-]+$/i', $val),
'PTR' => (bool)preg_match('/\.(in-addr|ip6)\.arpa$/i', $val),
'MX' => (bool)preg_match('/^\d+\s+[a-z0-9._-]+$/i', $val),
'SRV' => (bool)preg_match('/^\d+\s+\d+\s+\d+\s+[a-z0-9._-]+$/i', $val),
'TXT' => strlen($val) > 0,
default => true,
};
}
/** Vergleich je Typ robust normalisieren (nicht genutzt, bleibt aber da) */
private function compareDns(string $type, string $expected, string $actual): bool
{
$type = strtoupper($type);
$norm = function (string $s): string {
$s = trim($s);
$s = trim($s, "\"'");
$s = preg_replace('/\s+/', ' ', $s);
$s = rtrim($s, '.');
return $s;
};
if ($type === 'TXT') {
$parts = array_map(
fn($line) => $norm($line),
preg_split('/\R+/', $actual)
);
$actualFlat = implode('', array_map(fn($p) => str_replace(['"',' '], '', $p), $parts));
$expectedFlat = str_replace(['"',' '], '', $norm($expected));
return stripos($actualFlat, $expectedFlat) !== false;
}
if ($type === 'MX') {
$ax = array_filter(array_map('trim', preg_split('/\R+/', $actual)));
foreach ($ax as $line) {
$line = $norm($line);
if (stripos($line, $norm($expected)) !== false) return true;
}
return false;
}
if (in_array($type, ['A','AAAA','CNAME','PTR','SRV','TLSA'], true)) {
$ax = array_filter(array_map('trim', preg_split('/\R+/', $actual)));
$exp = $norm($expected);
foreach ($ax as $line) {
if ($norm($line) === $exp) return true;
}
if (in_array($type, ['CNAME','SRV','TLSA'], true)) {
foreach ($ax as $line) {
if (stripos($norm($line), rtrim($exp, '.')) !== false) return true;
}
}
return false;
}
return stripos($actual, $expected) !== false;
}
private function extractZone(string $fqdn): string
{
$fqdn = strtolower(trim($fqdn, "."));
$parts = explode('.', $fqdn);
$n = count($parts);
return $n >= 2 ? $parts[$n - 2] . '.' . $parts[$n - 1] : $fqdn;
}
private function ptrFromIPv4(string $ip): string
{
$p = array_reverse(explode('.', $ip));
return implode('.', $p) . '.in-addr.arpa';
}
private function ptrFromIPv6(string $ip): string
{
$expanded = strtolower(inet_ntop(inet_pton($ip)));
$hex = str_replace(':', '', $expanded);
return implode('.', array_reverse(str_split($hex))) . '.ip6.arpa';
}
public function render()
{
return view('livewire.ui.domain.modal.domain-dns-modal');
}
}
//namespace App\Livewire\Ui\Domain\Modal;
//
//use App\Models\Domain;
//use App\Models\TlsaRecord;
//use App\Support\NetProbe;
//use Illuminate\Support\Facades\DB;
//use Livewire\Attributes\On;
//use LivewireUI\Modal\ModalComponent;
//
//class DomainDnsModal extends ModalComponent
//{
// public int $domainId;
// public string $domainName = '';
// public string $zone = '';
// public string $ttl = '3600';
// public array $recordColors = [];
//
// /** @var array<int,array<string,string|int|null>> */
// public array $static = [];
// /** @var array<int,array<string,string|int|null>> */
// public array $dynamic = [];
// /** @var array<int,array<string,string|int|null>> */
// public array $optional = [];
//
// /** Farbklassen für den Status */
// public array $stateColors = [
// 'ok' => 'border-emerald-400/30 bg-emerald-500/10',
// 'missing' => 'border-rose-400/30 bg-rose-500/10',
// 'syntax' => 'border-amber-400/30 bg-amber-500/10',
// 'neutral' => 'border-white/10 bg-white/5',
// ];
//
// /** erst nach Klick färben */
// public bool $checked = false;
//
// public static function modalMaxWidth(): string { return '6xl'; }
//
// public function mount(int $domainId): void
// {
// $this->recordColors = [
// 'A' => 'bg-cyan-500/20 text-cyan-300',
// 'AAAA' => 'bg-blue-500/20 text-blue-300',
// 'MX' => 'bg-emerald-500/20 text-emerald-300',
// 'CNAME' => 'bg-indigo-500/20 text-indigo-300',
// 'PTR' => 'bg-amber-500/20 text-amber-300',
// 'TXT' => 'bg-violet-500/20 text-violet-300',
// 'SRV' => 'bg-rose-500/20 text-rose-300',
// 'TLSA' => 'bg-red-500/20 text-red-300',
// 'OPTIONAL' => 'bg-gray-500/20 text-gray-300',
// ];
//
// $d = Domain::findOrFail($domainId);
// $this->domainId = $domainId;
// $this->domainName = $d->domain;
//
// $tlsa = TlsaRecord::forServer()->where('service', '_25._tcp')->latest('id')->first();
//
// $ips = NetProbe::resolve();
// $ipv4 = $ips['ipv4'];
// $ipv6 = $ips['ipv6'];
//
// $this->zone = $this->extractZone($d->domain);
// $mta_sub = env('MTA_SUB');
// $base = env('BASE_DOMAIN');
// $mailServerFqdn = "{$mta_sub}.{$base}";
//
// // --- Infrastruktur (global) ---
// $this->static = [
// ['type' => 'A', 'name' => $mailServerFqdn, 'value' => $ipv4],
// ['type' => 'PTR', 'name' => $this->ptrFromIPv4($ipv4), 'value' => $mailServerFqdn],
// ];
// if ($ipv6) {
// $this->static[] = ['type' => 'AAAA','name' => $mailServerFqdn, 'value' => $ipv6];
// $this->static[] = ['type' => 'PTR', 'name' => $this->ptrFromIPv6($ipv6), 'value' => $mailServerFqdn];
// }
// if ($tlsa?->dns_string) {
// $this->static[] = [
// 'type' => 'TLSA',
// 'name' => "{$tlsa->service}.{$tlsa->host}",
// 'value' => "{$tlsa->usage} {$tlsa->selector} {$tlsa->matching} {$tlsa->hash}",
// ];
// }
//
// // --- Domain-spezifisch ---
// $spf = "v=spf1 ip4:{$ipv4}" . ($ipv6 ? " ip6:{$ipv6}" : '') . " mx -all";
// $dmarc = "v=DMARC1; p=none; rua=mailto:dmarc@{$this->domainName}; pct=100";
//
// $dkim = DB::table('dkim_keys')->where('domain_id', $d->id)->where('is_active', 1)->orderByDesc('id')->first();
// $selector = $dkim ? $dkim->selector : 'mwl1';
// $dkimHost = "{$selector}._domainkey.{$this->domainName}";
// $dkimTxt = $dkim && !str_starts_with(trim($dkim->public_key_txt), 'v=')
// ? 'v=DKIM1; k=rsa; p=' . trim($dkim->public_key_txt)
// : (trim($dkim->public_key_txt ?? '') ?: 'v=DKIM1; k=rsa; p=');
//
// $this->dynamic = [
// ['type' => 'MX', 'name' => $this->domainName, 'value' => "10 {$mailServerFqdn}."],
// ['type' => 'CNAME', 'name' => "autoconfig.{$this->domainName}", 'value' => "{$mailServerFqdn}."],
// ['type' => 'CNAME', 'name' => "autodiscover.{$this->domainName}",'value' => "{$mailServerFqdn}."],
// ['type' => 'TXT', 'name' => $this->domainName, 'value' => $spf],
// ['type' => 'TXT', 'name' => "_dmarc.{$this->domainName}", 'value' => $dmarc],
// ['type' => 'TXT', 'name' => $dkimHost, 'value' => $dkimTxt],
// ];
//
// $this->optional = [
// ['type' => 'SRV', 'name' => "_autodiscover._tcp.{$this->domainName}", 'value' => "0 0 443 {$mailServerFqdn}."],
// ['type' => 'SRV', 'name' => "_imaps._tcp.{$this->domainName}", 'value' => "0 0 993 {$mailServerFqdn}."],
// ['type' => 'SRV', 'name' => "_pop3s._tcp.{$this->domainName}", 'value' => "0 0 995 {$mailServerFqdn}."],
// ['type' => 'SRV', 'name' => "_submission._tcp.{$this->domainName}", 'value' => "0 0 587 {$mailServerFqdn}."],
// ];
// }
//
// private function applyNeutralColors(): void
// {
// foreach (['static', 'dynamic', 'optional'] as $group) {
// $this->{$group} = array_map(fn($r) => array_merge($r, [
// 'state' => 'neutral',
// 'boxClass' => $this->stateColors['neutral'],
// 'actual' => '',
// ]), $this->{$group});
// }
// }
//
// /** Klick im Footer löst diesen Check aus */
// #[On('domain:check-dns')]
// public function checkDns(): void
// {
// // dynamische (pflicht) Records prüfen
// foreach ($this->dynamic as $i => $r) {
// $actual = $this->dig($r['type'], $r['name']);
// $state = $this->stateFor($r['type'], $r['value'], $actual, optional:false);
// $this->dynamic[$i]['actual'] = $actual;
// $this->dynamic[$i]['state'] = $state;
// }
//
// // statische (pflicht) Records prüfen (TLSA, PTR, A/AAAA …)
// foreach ($this->static as $i => $r) {
// $actual = $this->dig($r['type'], $r['name']);
// $state = $this->stateFor($r['type'], $r['value'], $actual, optional:false);
// $this->static[$i]['actual'] = $actual;
// $this->static[$i]['state'] = $state;
// }
//
// // optionale Records: nie „missing“, nur neutral|syntax|ok
// foreach ($this->optional as $i => $r) {
// $actual = $this->dig($r['type'], $r['name']);
// $state = $this->stateFor($r['type'], $r['value'], $actual, optional:true);
// $this->optional[$i]['actual'] = $actual;
// $this->optional[$i]['state'] = $state;
// }
//
// $this->checked = true;
// }
//
// /** Prüft eine Record-Liste und gibt die Liste mit 'state' zurück */
// private function checkGroup(array $group, bool $optional = false): array
// {
// foreach ($group as &$r) {
// $type = (string)$r['type'];
// $name = (string)$r['name'];
// $exp = (string)$r['value'];
//
// $act = $this->dig($type, $name);
//
// if ($act === '') {
// $r['state'] = $optional ? 'missing' : 'missing';
// continue;
// }
//
// $ok = $this->compareDns($type, $exp, $act);
// $r['state'] = $ok ? 'ok' : 'mismatch';
// }
// return $group;
// }
//
// /** dig wrapper */
// /* ---------- DNS & Bewertung ---------- */
//
// private function dig(string $type, string $name): string
// {
// $type = strtoupper($type);
// $name = rtrim($name, '.'); // wir arbeiten intern ohne trailing dot
// $out = @shell_exec('dig +timeout=2 +tries=1 +short '
// . escapeshellarg($name) . ' ' . escapeshellarg($type) . ' 2>/dev/null') ?? '';
// $out = trim($out);
//
// // Mehrzeiliges TXT zu einer Zeile squashen, Quotes weg
// if ($type === 'TXT' && $out !== '') {
// $lines = array_filter(array_map('trim', explode("\n", $out)));
// $joined = implode('', array_map(fn($l)=>trim($l,'"'), $lines));
// return $joined;
// }
//
// // Nur erste Zeile vergleichen (mehrere Antworten -> erste reicht)
// if ($out !== '') {
// $out = trim(explode("\n", $out)[0]);
// // MX/SRV/CNAME Ziele ohne trailing dot vergleichen
// if (in_array($type, ['MX','CNAME','SRV'])) {
// $out = rtrim($out, '.');
// }
// }
// return $out;
// }
//
// private function stateFor(string $type, string $expected, string $actual, bool $optional): string
// {
// if ($actual === '') {
// return $optional ? 'neutral' : 'missing';
// }
//
// $type = strtoupper($type);
// $exp = $this->normExpected($type, $expected);
// $act = $this->normActual($type, $actual);
//
// // Syntax plausibilisieren
// $syntaxOk = $this->validateSyntax($type, $act);
// if (!$syntaxOk) return 'syntax';
//
// // Bei TXT/Policies: inhaltlich locker matchen (Beginn mit v=…)
// if ($type === 'TXT') {
// // SPF
// if (str_starts_with(strtoupper($exp), 'V=SPF1')) {
// return str_starts_with(strtoupper($act), 'V=SPF1') ? 'ok' : 'syntax';
// }
// // DMARC
// if (str_starts_with(strtoupper($exp), 'V=DMARC1')) {
// return str_starts_with(strtoupper($act), 'V=DMARC1') ? 'ok' : 'syntax';
// }
// // DKIM
// if (str_starts_with(strtoupper($exp), 'V=DKIM1')) {
// return str_starts_with(strtoupper($act), 'V=DKIM1') ? 'ok' : 'syntax';
// }
// return ($act !== '') ? 'ok' : ($optional ? 'neutral' : 'missing');
// }
//
// // MX: wir erwarten „prio host“ wir prüfen nur, ob host passt
// if ($type === 'MX') {
// // $act z.B. "10 mx.example.tld"
// $parts = preg_split('/\s+/', $act);
// $host = strtolower($parts[1] ?? $act);
// $expHost = strtolower(preg_replace('/^\d+\s+/', '', $exp));
// return ($host === $expHost) ? 'ok' : 'syntax';
// }
//
// // SRV: „prio weight port host“ prüfen Port + Host grob
// if ($type === 'SRV') {
// $ap = preg_split('/\s+/', $act);
// $ep = preg_split('/\s+/', $exp);
// if (count($ap) >= 4 && count($ep) >= 4) {
// $aport = (int)$ap[2];
// $eport = (int)$ep[2];
// $ahost = strtolower(rtrim(end($ap), '.'));
// $ehost = strtolower(rtrim(end($ep), '.'));
// return ($aport === $eport && $ahost === $ehost) ? 'ok' : 'syntax';
// }
// return 'syntax';
// }
//
// // CNAME/A/AAAA/PTR/TLSA: einfacher Gleichheitsvergleich (normalisiert)
// return ($act === $exp) ? 'ok' : 'syntax';
// }
//
// private function normExpected(string $type, string $v): string
// {
// $v = trim($v);
// $t = strtoupper($type);
// if (in_array($t, ['MX','CNAME','SRV'])) $v = rtrim($v, '.');
// if ($t === 'PTR') $v = strtolower(rtrim($v, '.'));
// if ($t === 'TLSA') $v = preg_replace('/\s+/', ' ', $v);
// return $v;
// }
//
// private function normActual(string $type, string $v): string
// {
// $v = trim($v);
// $t = strtoupper($type);
// if (in_array($t, ['MX','CNAME','SRV'])) $v = rtrim($v, '.');
// if ($t === 'PTR') $v = strtolower(rtrim($v, '.'));
// if ($t === 'TLSA') $v = preg_replace('/\s+/', ' ', $v);
// return $v;
// }
//
// private function validateSyntax(string $type, string $val): bool
// {
// $t = strtoupper($type);
// if ($val === '') return false;
//
// return match ($t) {
// 'A' => (bool)filter_var($val, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4),
// 'AAAA' => (bool)filter_var($val, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6),
// 'CNAME' => (bool)preg_match('/^[a-z0-9._-]+$/i', $val),
// 'PTR' => (bool)preg_match('/\.(in-addr|ip6)\.arpa$/i', $val),
// 'MX' => (bool)preg_match('/^\d+\s+[a-z0-9._-]+$/i', $val),
// 'SRV' => (bool)preg_match('/^\d+\s+\d+\s+\d+\s+[a-z0-9._-]+$/i', $val),
// 'TLSA' => (bool)preg_match('/^[0-3]\s+[01]\s+[123]\s+[0-9a-f]{32,}$/i', $val),
// 'TXT' => strlen($val) > 0, // SPF/DMARC/DKIM oben extra geprüft
// default => true,
// };
// }
//
// /** Vergleich je Typ robust normalisieren */
// private function compareDns(string $type, string $expected, string $actual): bool
// {
// $type = strtoupper($type);
//
// // Normalisierer
// $norm = function (string $s): string {
// $s = trim($s);
// $s = trim($s, "\"'"); // TXT quotes
// $s = preg_replace('/\s+/', ' ', $s); // unify spaces
// $s = rtrim($s, '.'); // trailing dot
// return $s;
// };
//
// // TXT kann aus mehreren quoted Strings bestehen (mehrere Zeilen)
// if ($type === 'TXT') {
// // dig kann mehrere Zeilen liefern, jede in Quotes zusammenfügen
// $parts = array_map(
// fn($line) => $norm($line),
// preg_split('/\R+/', $actual)
// );
// $actualFlat = implode('', array_map(fn($p) => str_replace(['"',' '], '', $p), $parts));
// $expectedFlat = str_replace(['"',' '], '', $norm($expected));
// return stripos($actualFlat, $expectedFlat) !== false;
// }
//
// if ($type === 'MX') {
// // Erwartet i.d.R. "10 host." wir normalisieren beide Seiten
// $ax = array_filter(array_map('trim', preg_split('/\R+/', $actual)));
// foreach ($ax as $line) {
// $line = $norm($line);
// if (stripos($line, $norm($expected)) !== false) return true;
// }
// return false;
// }
//
// if (in_array($type, ['A','AAAA','CNAME','PTR','SRV','TLSA'], true)) {
// $ax = array_filter(array_map('trim', preg_split('/\R+/', $actual)));
// $exp = $norm($expected);
// foreach ($ax as $line) {
// if ($norm($line) === $exp) return true;
// }
// // bei CNAME/SRV/TLSA reicht oft „enthält denselben Zielhost“
// if (in_array($type, ['CNAME','SRV','TLSA'], true)) {
// foreach ($ax as $line) {
// if (stripos($norm($line), rtrim($exp, '.')) !== false) return true;
// }
// }
// return false;
// }
//
// // Fallback: substring
// return stripos($actual, $expected) !== false;
// }
//
// private function extractZone(string $fqdn): string
// {
// $fqdn = strtolower(trim($fqdn, "."));
// $parts = explode('.', $fqdn);
// $n = count($parts);
// return $n >= 2 ? $parts[$n - 2] . '.' . $parts[$n - 1] : $fqdn;
// }
//
// private function ptrFromIPv4(string $ip): string
// {
// $p = array_reverse(explode('.', $ip));
// return implode('.', $p) . '.in-addr.arpa';
// }
//
// private function ptrFromIPv6(string $ip): string
// {
// $expanded = strtolower(inet_ntop(inet_pton($ip)));
// $hex = str_replace(':', '', $expanded);
// return implode('.', array_reverse(str_split($hex))) . '.ip6.arpa';
// }
//
// public function render()
// {
// return view('livewire.ui.domain.modal.domain-dns-modal');
// }
//}
//namespace App\Livewire\Ui\Domain\Modal;
//
//use App\Models\Domain;
//use App\Models\TlsaRecord;
//use App\Support\NetProbe;
//use Illuminate\Support\Facades\DB;
//use Livewire\Attributes\On;
//use LivewireUI\Modal\ModalComponent;
//
//class DomainDnsModal extends ModalComponent
//{
// public int $domainId;
// public string $domainName = '';
// public string $zone = '';
// public string $ttl = '3600';
// public array $recordColors = [];
//
// /** @var array<int,array<string,string|int|null>> */
// public array $static = [];
// /** @var array<int,array<string,string|int|null>> */
// public array $dynamic = [];
// /** @var array<int,array<string,string|int|null>> */
// public array $optional = [];
//
// public array $stateColors = [
// 'ok' => 'text-emerald-300 bg-emerald-500/10 border-emerald-400/30',
// 'missing' => 'text-rose-300 bg-rose-500/10 border-rose-400/30',
// 'mismatch' => 'text-amber-200 bg-amber-500/10 border-amber-400/30',
// ];
//
// public static function modalMaxWidth(): string
// {
// return '6xl';
// }
//
// public function mount(int $domainId): void
// {
// $this->recordColors = [
// 'A' => 'bg-cyan-500/20 text-cyan-300',
// 'AAAA' => 'bg-blue-500/20 text-blue-300',
// 'MX' => 'bg-emerald-500/20 text-emerald-300',
// 'CNAME' => 'bg-indigo-500/20 text-indigo-300',
// 'PTR' => 'bg-amber-500/20 text-amber-300',
// 'TXT' => 'bg-violet-500/20 text-violet-300',
// 'SRV' => 'bg-rose-500/20 text-rose-300',
// 'TLSA' => 'bg-red-500/20 text-red-300',
// 'OPTIONAL' => 'bg-gray-500/20 text-gray-300',
// ];
//
// $d = Domain::findOrFail($domainId);
// $this->domainName = $d->domain;
//
// $tlsa = TlsaRecord::forServer()
// ->where('service', '_25._tcp')
// ->latest('id')
// ->first();
//
// $ips = NetProbe::resolve();
// $ipv4 = $ips['ipv4'];
// $ipv6 = $ips['ipv6'];
//
// $this->zone = $this->extractZone($d->domain);
// $mta_sub = env('MTA_SUB');
// $base = env('BASE_DOMAIN');
// $mailServerFqdn = $mta_sub . '.' . $base;
// $mta = $mta_sub . '.' . $this->zone;
//
// // --- Statische Infrastruktur (für alle Domains gleich) ---
// $this->static = [
// ['type' => 'A', 'name' => $mailServerFqdn, 'value' => $ipv4],
// ['type' => 'PTR', 'name' => $this->ptrFromIPv4($ipv4), 'value' => $tlsa->host],
// ];
// if ($ipv6) {
// $this->static[] = ['type' => 'AAAA', 'name' => $mailServerFqdn, 'value' => $ipv6];
// $this->static[] = ['type' => 'PTR', 'name' => $this->ptrFromIPv6($ipv6), 'value' => $mailServerFqdn];
// }
//
//
// if ($tlsa?->dns_string) {
// $this->static[] = [
// 'type' => 'TLSA',
// 'name' => "{$tlsa->service}.{$tlsa->host}",
// 'value' => "{$tlsa->usage} {$tlsa->selector} {$tlsa->matching} {$tlsa->hash}",
// ];
// }
//
// // --- Domain-spezifisch ---
// $spf = "v=spf1 ip4:{$ipv4} ip6:{$ipv6} mx -all";
// $dmarc = "v=DMARC1; p=none; rua=mailto:dmarc@{$this->domainName}; pct=100";
//
// $dkim = DB::table('dkim_keys')
// ->where('domain_id', $d->id)->where('is_active', 1)->orderByDesc('id')->first();
// $selector = $dkim ? $dkim->selector : 'mwl1';
// $dkimHost = "{$selector}._domainkey.{$this->domainName}";
// $dkimTxt = $dkim && !str_starts_with(trim($dkim->public_key_txt), 'v=')
// ? 'v=DKIM1; k=rsa; p=' . $dkim->public_key_txt
// : ($dkim->public_key_txt ?? 'v=DKIM1; k=rsa; p=');
//
// $this->dynamic = [
// ['type' => 'MX', 'name' => $this->domainName, 'value' => "10 {$mailServerFqdn}."],
//
// ['type' => 'CNAME', 'name' => "autoconfig.$this->domainName", 'value' => "$mailServerFqdn."],
// ['type' => 'CNAME', 'name' => "autodiscover.$this->domainName", 'value' => "$mailServerFqdn."],
//
// // TXT Records
// ['type' => 'TXT', 'name' => $this->domainName, 'value' => $spf, 'helpLabel' => 'SPF Record Syntax', 'helpUrl' => 'http://www.open-spf.org/SPF_Record_Syntax/'],
// ['type' => 'TXT', 'name' => "_dmarc.{$this->domainName}", 'value' => $dmarc, 'helpLabel' => 'DMARC Assistant', 'helpUrl' => 'https://www.kitterman.com/dmarc/assistant.html'],
// ['type' => 'TXT', 'name' => $dkimHost, 'value' => $dkimTxt, 'helpLabel' => 'DKIM Inspector', 'helpUrl' => 'https://dkimvalidator.com/'],
//
// ];
//
// $this->optional = [
// // SRV Records für Autodiscover und Maildienste
// ['type' => 'SRV', 'name' => "_autodiscover._tcp.$this->domainName", 'value' => "0 0 443 {$mailServerFqdn}."],
// ['type' => 'SRV', 'name' => "_imaps._tcp.$this->domainName", 'value' => "0 0 993 {$mailServerFqdn}."],
// ['type' => 'SRV', 'name' => "_pop3s._tcp.$this->domainName", 'value' => "0 0 995 {$mailServerFqdn}."],
// ['type' => 'SRV', 'name' => "_submission._tcp.$this->domainName", 'value' => "0 0 587 {$mailServerFqdn}."],
// ];
//
// }
//
// #[On('domain:check-dns')]
// public function checkDns(): void
// {
// $this->validateDnsRecords();
// }
//
//
// private function validateDnsRecords(): void
// {
// // alle Listen zusammenführen
// $records = array_merge($this->static, $this->dynamic);
//
// foreach ($records as &$record) {
// $type = $record['type'];
// $name = $record['name'];
// $expected = trim((string) $record['value']);
//
// $result = @shell_exec("dig +short {$type} {$name} 2>/dev/null");
// $result = trim((string) $result);
//
// if ($result === '') {
// $record['state'] = 'missing';
// } elseif (stripos($result, trim($expected, '.')) !== false) {
// $record['state'] = 'ok';
// } else {
// $record['state'] = 'mismatch';
// }
// }
//
// $this->static = array_values(array_filter($records, fn($r) => in_array($r['type'], ['A','AAAA','PTR','TLSA'])));
// $this->dynamic = array_values(array_filter($records, fn($r) => !in_array($r['type'], ['A','AAAA','PTR','TLSA'])));
// }
//
// private function extractZone(string $fqdn): string
// {
// $fqdn = strtolower(trim($fqdn, "."));
// $parts = explode('.', $fqdn);
// $n = count($parts);
// return $n >= 2 ? $parts[$n - 2] . '.' . $parts[$n - 1] : $fqdn; // nimmt die letzten 2 Labels
// }
//
// private function detectIPv4(): string
// {
// // robust & ohne env
// $out = @shell_exec("ip -4 route get 1.1.1.1 2>/dev/null | awk '{for(i=1;i<=NF;i++) if(\$i==\"src\"){print \$(i+1); exit}}'");
// $ip = trim((string)$out);
// if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) return $ip;
// $ip = trim($_SERVER['SERVER_ADDR'] ?? '');
// return $ip ?: '203.0.113.10'; // Fallback Demo
// }
//
// private function detectIPv6(): ?string
// {
// $out = @shell_exec("ip -6 route get 2001:4860:4860::8888 2>/dev/null | awk '{for(i=1;i<=NF;i++) if(\$i==\"src\"){print \$(i+1); exit}}'");
// $ip = trim((string)$out);
// return filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6) ? $ip : null;
// }
//
// private function ptrFromIPv4(string $ip): string
// {
// $p = array_reverse(explode('.', $ip));
// return implode('.', $p) . '.in-addr.arpa';
// }
//
// private function ptrFromIPv6(string $ip): string
// {
// $expanded = strtolower(inet_ntop(inet_pton($ip)));
// $hex = str_replace(':', '', $expanded);
// return implode('.', array_reverse(str_split($hex))) . '.ip6.arpa';
// }
//
// public function render()
// {
// return view('livewire.ui.domain.modal.domain-dns-modal');
// }
//
//// public int $domainId;
//// public Domain $domain;
////
//// public array $records = [];
////
//// public function mount(int $domainId): void
//// {
//// $this->domainId = $domainId;
//// $this->domain = Domain::findOrFail($domainId);
////
//// // Placeholder-Werte, sofern du sie anderswo speicherst gern ersetzen:
//// $serverIp = config('app.server_ip', 'DEINE.SERVER.IP');
//// $mxHost = config('mailwolt.mx_fqdn', 'mx.' . $this->domain->domain);
//// $selector = optional(
//// DkimKey::where('domain_id', $this->domain->id)->where('is_active', true)->first()
//// )->selector ?? 'mwl1';
////
//// $dkimTxt = optional(
//// DkimKey::where('domain_id', $this->domain->id)->where('is_active', true)->first()
//// )->public_key_txt ?? 'DKIM_PUBLIC_KEY';
////
//// $this->records = [
//// [
//// 'type' => 'A',
//// 'host' => $this->domain->domain,
//// 'value' => $serverIp,
//// 'ttl' => 3600,
//// ],
//// [
//// 'type' => 'MX',
//// 'host' => $this->domain->domain,
//// 'value' => "10 {$mxHost}.",
//// 'ttl' => 3600,
//// ],
//// [
//// 'type' => 'TXT',
//// 'host' => $this->domain->domain,
//// 'value' => 'v=spf1 mx a -all',
//// 'ttl' => 3600,
//// ],
//// [
//// 'type' => 'TXT',
//// 'host' => "{$selector}._domainkey." . $this->domain->domain,
//// // komplette, fertige TXT-Payload (aus deiner dkim_keys.public_key_txt Spalte)
//// 'value' => "v=DKIM1; k=rsa; p={$dkimTxt}",
//// 'ttl' => 3600,
//// ],
//// [
//// 'type' => 'TXT',
//// 'host' => "_dmarc." . $this->domain->domain,
//// 'value' => "v=DMARC1; p=none; rua=mailto:dmarc@" . $this->domain->domain . "; pct=100",
//// 'ttl' => 3600,
//// ],
//// ];
//// }
////
//// public static function modalMaxWidth(): string
//// {
//// return '4xl'; // schön breit
//// }
////
//// public function render()
//// {
//// return view('livewire.ui.domain.modal.domain-dns-modal');
//// }
//
//// public Domain $domain;
////
//// public static function modalMaxWidth(): string
//// {
//// return '3xl';
//// }
////
//// public function mount(int $domainId): void
//// {
//// $this->domain = Domain::with(['dkimKeys' /* falls Relationen existieren */])->findOrFail($domainId);
//// }
////
//// public function render()
//// {
//// // Hier kannst du die Records vorbereiten (SPF/DMARC/DKIM)
//// $records = [
//// [
//// 'type' => 'TXT',
//// 'host' => $this->domain->domain,
//// 'value'=> $this->domain->spf_record ?? 'v=spf1 mx a -all',
//// 'ttl' => 3600,
//// ],
//// [
//// 'type' => 'TXT',
//// 'host' => '_dmarc.' . $this->domain->domain,
//// 'value'=> $this->domain->dmarc_record ?? 'v=DMARC1; p=none; rua=mailto:dmarc@' . $this->domain->domain . '; pct=100',
//// 'ttl' => 3600,
//// ],
//// // DKIM (falls vorhanden)
//// // Beispiel: nimm den aktiven Key oder den ersten
//// ];
////
//// $dkim = optional($this->domain->dkimKeys()->where('is_active', true)->first() ?? $this->domain->dkimKeys()->first());
//// if ($dkim && $dkim->selector) {
//// $records[] = [
//// 'type' => 'TXT',
//// 'host' => "{$dkim->selector}._domainkey." . $this->domain->domain,
//// 'value'=> "v=DKIM1; k=rsa; p={$dkim->public_key_txt}",
//// 'ttl' => 3600,
//// ];
//// }
////
//// return view('livewire.ui.domain.modal.domain-dns-modal', [
//// 'records' => $records,
//// ]);
//// }
//
//// public function render()
//// {
//// return view('livewire.ui.domain.modal.domain-dns-modal');
//// }
//}