parent
0cb7212d4b
commit
3c1093311c
|
|
@ -103,13 +103,6 @@ class DomainDnsModal extends ModalComponent
|
|||
['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 ---
|
||||
[
|
||||
|
|
@ -587,6 +580,8 @@ class DomainDnsModal extends ModalComponent
|
|||
return view('livewire.ui.domain.modal.domain-dns-modal');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//namespace App\Livewire\Ui\Domain\Modal;
|
||||
//
|
||||
//use App\Models\Domain;
|
||||
|
|
|
|||
|
|
@ -2,16 +2,16 @@
|
|||
|
||||
namespace App\Livewire\Ui\Mail;
|
||||
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
use App\Models\Domain;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class DnsHealthCard extends Component
|
||||
{
|
||||
public array $rows = []; // [{id,name,ok,missing:[...]}]
|
||||
public string $mtaHost = ''; // z.B. mx.nexlab.at
|
||||
public bool $tlsa = false; // hostweit
|
||||
public string $mtaHost = '';
|
||||
public bool $tlsa = false;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
|
|
@ -35,51 +35,66 @@ class DnsHealthCard extends Component
|
|||
|
||||
protected function load(bool $force = false): void
|
||||
{
|
||||
[$this->mtaHost, $this->tlsa, $this->rows] = Cache::remember('dash.dnshealth.v1', $force ? 1 : 600, function () {
|
||||
[$this->mtaHost, $this->tlsa, $this->rows] = Cache::remember('dash.dnshealth.v2', $force ? 1 : 600, function () {
|
||||
|
||||
$base = trim((string)env('BASE_DOMAIN', ''));
|
||||
$mtaSub = trim((string)env('MTA_SUB', 'mx'));
|
||||
$mtaHost = $base !== '' ? "{$mtaSub}.{$base}" : $mtaSub;
|
||||
$base = trim((string) env('BASE_DOMAIN', ''));
|
||||
$mtaSub = trim((string) env('MTA_SUB', 'mx'));
|
||||
$mtaHost = $base !== '' ? "{$mtaSub}.{$base}" : $mtaSub; // z.B. mx.nexlab.at
|
||||
|
||||
$rows = [];
|
||||
// ▼ gewünschter Filter:
|
||||
$domains = Domain::query()
|
||||
->where('is_system', false)
|
||||
->where('is_active', true)
|
||||
->where(function ($q) {
|
||||
$q->where('is_system', false)
|
||||
->orWhere(function ($q2) {
|
||||
$q2->where('is_system', true)->where('is_server', false);
|
||||
});
|
||||
})
|
||||
->orderBy('domain')
|
||||
->get(['id', 'domain']);
|
||||
|
||||
$rows = [];
|
||||
foreach ($domains as $d) {
|
||||
$dom = $d->domain;
|
||||
|
||||
// DKIM-Selector ermitteln: .env > DB > Fallback null
|
||||
$selector = trim((string) env('DKIM_SELECTOR', ''));
|
||||
if ($selector === '') {
|
||||
$selector = (string) DB::table('dkim_keys')
|
||||
->where('domain_id', $d->id)
|
||||
->where('is_active', 1)
|
||||
->orderByDesc('id')
|
||||
->value('selector') ?? '';
|
||||
}
|
||||
|
||||
$missing = [];
|
||||
|
||||
// Pflicht-Checks
|
||||
if (!$this->mxPointsTo($dom, [$mtaHost])) $missing[] = 'MX';
|
||||
if (!$this->hasSpf($dom)) $missing[] = 'SPF';
|
||||
if (!$this->hasDkim($dom)) $missing[] = 'DKIM';
|
||||
if (!$this->hasTxt("_dmarc.$dom")) $missing[] = 'DMARC';
|
||||
if (!$this->hasSpf($dom)) $missing[] = 'SPF';
|
||||
if (!$this->hasDkim($dom, $selector)) $missing[] = 'DKIM';
|
||||
if (!$this->hasTxt("_dmarc.$dom")) $missing[] = 'DMARC';
|
||||
|
||||
$rows[] = [
|
||||
'id' => (int)$d->id,
|
||||
'name' => $dom,
|
||||
'ok' => empty($missing),
|
||||
'id' => (int) $d->id,
|
||||
'name' => $dom,
|
||||
'ok' => empty($missing),
|
||||
'missing' => $missing,
|
||||
];
|
||||
}
|
||||
|
||||
// TLSA (hostweit, nur Info)
|
||||
// Hostweites TLSA (nur Hinweis)
|
||||
$tlsa = $this->hasTlsa("_25._tcp.$mtaHost") || $this->hasTlsa("_465._tcp.$mtaHost") || $this->hasTlsa("_587._tcp.$mtaHost");
|
||||
|
||||
return [$mtaHost, $tlsa, $rows];
|
||||
});
|
||||
}
|
||||
|
||||
/* ── DNS Helpers (mit Timeout, damit UI nicht hängt) ───────────────── */
|
||||
/* ── DNS Helpers ───────────────────────────────────────────────────── */
|
||||
|
||||
protected function digShort(string $type, string $name): string
|
||||
{
|
||||
$cmd = "timeout 2 dig +short " . escapeshellarg($name) . " " . escapeshellarg(strtoupper($type)) . " 2>/dev/null";
|
||||
return (string)@shell_exec($cmd) ?: '';
|
||||
$cmd = "timeout 2 dig +short " . escapeshellarg($name) . ' ' . escapeshellarg(strtoupper($type)) . " 2>/dev/null";
|
||||
return (string) @shell_exec($cmd) ?: '';
|
||||
}
|
||||
|
||||
protected function hasTxt(string $name): bool
|
||||
|
|
@ -101,11 +116,13 @@ class DnsHealthCard extends Component
|
|||
return false;
|
||||
}
|
||||
|
||||
// DKIM: wenn spezifischer Selector vorhanden → prüfe den, sonst akzeptiere _domainkey-Policy als “vorhanden”
|
||||
protected function hasDkim(string $domain): bool
|
||||
// ▼ DKIM: bevorzugt konkreten Selector prüfen; wenn leer, versuche Policy (_domainkey)
|
||||
protected function hasDkim(string $domain, string $selector = ''): bool
|
||||
{
|
||||
$sel = trim((string)env('DKIM_SELECTOR', ''));
|
||||
if ($sel !== '' && $this->hasTxt("{$sel}._domainkey.$domain")) return true;
|
||||
if ($selector !== '') {
|
||||
return $this->hasTxt("{$selector}._domainkey.$domain");
|
||||
}
|
||||
// Manche Betreiber veröffentlichen eine Policy auf _domainkey.<dom>
|
||||
return $this->hasTxt("_domainkey.$domain");
|
||||
}
|
||||
|
||||
|
|
@ -116,21 +133,153 @@ class DnsHealthCard extends Component
|
|||
|
||||
$targets = [];
|
||||
foreach (preg_split('/\R+/', trim($out)) as $line) {
|
||||
// Format: "10 mx.example.com."
|
||||
if (preg_match('~\s+([A-Za-z0-9\.\-]+)\.?$~', trim($line), $m)) {
|
||||
$targets[] = strtolower($m[1]);
|
||||
}
|
||||
}
|
||||
if (!$targets) return false;
|
||||
|
||||
$allowed = array_map('strtolower', $allowedHosts);
|
||||
$allowed = array_map(fn ($h) => strtolower(rtrim($h, '.')), $allowedHosts);
|
||||
foreach ($targets as $t) {
|
||||
$t = rtrim($t, '.');
|
||||
if (in_array($t, $allowed, true)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//namespace App\Livewire\Ui\Mail;
|
||||
//
|
||||
//use Livewire\Attributes\On;
|
||||
//use Livewire\Component;
|
||||
//use App\Models\Domain;
|
||||
//use Illuminate\Support\Facades\Cache;
|
||||
//
|
||||
//class DnsHealthCard extends Component
|
||||
//{
|
||||
// public array $rows = []; // [{id,name,ok,missing:[...]}]
|
||||
// public string $mtaHost = ''; // z.B. mx.nexlab.at
|
||||
// public bool $tlsa = false; // hostweit
|
||||
//
|
||||
// public function mount(): void
|
||||
// {
|
||||
// $this->load();
|
||||
// }
|
||||
//
|
||||
// public function render()
|
||||
// {
|
||||
// return view('livewire.ui.mail.dns-health-card');
|
||||
// }
|
||||
//
|
||||
// public function refresh(): void
|
||||
// {
|
||||
// $this->load(true);
|
||||
// }
|
||||
//
|
||||
// public function openDnsModal(int $domainId): void
|
||||
// {
|
||||
// $this->dispatch('openModal', component: 'ui.domain.modal.domain-dns-modal', arguments: ['domainId' => $domainId]);
|
||||
// }
|
||||
//
|
||||
// protected function load(bool $force = false): void
|
||||
// {
|
||||
// [$this->mtaHost, $this->tlsa, $this->rows] = Cache::remember('dash.dnshealth.v1', $force ? 1 : 600, function () {
|
||||
//
|
||||
// $base = trim((string)env('BASE_DOMAIN', ''));
|
||||
// $mtaSub = trim((string)env('MTA_SUB', 'mx'));
|
||||
// $mtaHost = $base !== '' ? "{$mtaSub}.{$base}" : $mtaSub;
|
||||
//
|
||||
// $rows = [];
|
||||
// $domains = Domain::query()
|
||||
// ->where('is_system', false)
|
||||
// ->where('is_active', true)
|
||||
// ->orderBy('domain')
|
||||
// ->get(['id', 'domain']);
|
||||
//
|
||||
// foreach ($domains as $d) {
|
||||
// $dom = $d->domain;
|
||||
//
|
||||
// $missing = [];
|
||||
//
|
||||
// // Pflicht-Checks
|
||||
// if (!$this->mxPointsTo($dom, [$mtaHost])) $missing[] = 'MX';
|
||||
// if (!$this->hasSpf($dom)) $missing[] = 'SPF';
|
||||
// if (!$this->hasDkim($dom)) $missing[] = 'DKIM';
|
||||
// if (!$this->hasTxt("_dmarc.$dom")) $missing[] = 'DMARC';
|
||||
//
|
||||
// $rows[] = [
|
||||
// 'id' => (int)$d->id,
|
||||
// 'name' => $dom,
|
||||
// 'ok' => empty($missing),
|
||||
// 'missing' => $missing,
|
||||
// ];
|
||||
// }
|
||||
//
|
||||
// // TLSA (hostweit, nur Info)
|
||||
// $tlsa = $this->hasTlsa("_25._tcp.$mtaHost") || $this->hasTlsa("_465._tcp.$mtaHost") || $this->hasTlsa("_587._tcp.$mtaHost");
|
||||
//
|
||||
// return [$mtaHost, $tlsa, $rows];
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// /* ── DNS Helpers (mit Timeout, damit UI nicht hängt) ───────────────── */
|
||||
//
|
||||
// protected function digShort(string $type, string $name): string
|
||||
// {
|
||||
// $cmd = "timeout 2 dig +short " . escapeshellarg($name) . " " . escapeshellarg(strtoupper($type)) . " 2>/dev/null";
|
||||
// return (string)@shell_exec($cmd) ?: '';
|
||||
// }
|
||||
//
|
||||
// protected function hasTxt(string $name): bool
|
||||
// {
|
||||
// return trim($this->digShort('TXT', $name)) !== '';
|
||||
// }
|
||||
//
|
||||
// protected function hasTlsa(string $name): bool
|
||||
// {
|
||||
// return trim($this->digShort('TLSA', $name)) !== '';
|
||||
// }
|
||||
//
|
||||
// protected function hasSpf(string $domain): bool
|
||||
// {
|
||||
// $out = $this->digShort('TXT', $domain);
|
||||
// foreach (preg_split('/\R+/', trim($out)) as $line) {
|
||||
// if (stripos($line, 'v=spf1') !== false) return true;
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//
|
||||
// // DKIM: wenn spezifischer Selector vorhanden → prüfe den, sonst akzeptiere _domainkey-Policy als “vorhanden”
|
||||
// protected function hasDkim(string $domain): bool
|
||||
// {
|
||||
// $sel = trim((string)env('DKIM_SELECTOR', ''));
|
||||
// if ($sel !== '' && $this->hasTxt("{$sel}._domainkey.$domain")) return true;
|
||||
// return $this->hasTxt("_domainkey.$domain");
|
||||
// }
|
||||
//
|
||||
// protected function mxPointsTo(string $domain, array $allowedHosts): bool
|
||||
// {
|
||||
// $out = $this->digShort('MX', $domain);
|
||||
// if ($out === '') return false;
|
||||
//
|
||||
// $targets = [];
|
||||
// foreach (preg_split('/\R+/', trim($out)) as $line) {
|
||||
// // Format: "10 mx.example.com."
|
||||
// if (preg_match('~\s+([A-Za-z0-9\.\-]+)\.?$~', trim($line), $m)) {
|
||||
// $targets[] = strtolower($m[1]);
|
||||
// }
|
||||
// }
|
||||
// if (!$targets) return false;
|
||||
//
|
||||
// $allowed = array_map('strtolower', $allowedHosts);
|
||||
// foreach ($targets as $t) {
|
||||
// if (in_array($t, $allowed, true)) return true;
|
||||
// }
|
||||
// return false;
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
//namespace App\Livewire\Ui\Mail;
|
||||
//
|
||||
//use Livewire\Component;
|
||||
|
|
|
|||
|
|
@ -1,15 +1,17 @@
|
|||
<div wire:poll.60s="refresh" class="glass-card p-5 rounded-2xl border border-white/10 bg-white/5">
|
||||
<div class="glass-card p-5 rounded-2xl border border-white/10 bg-white/5">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="inline-flex items-center gap-2 bg-white/5 border border-white/10 px-2.5 py-1 rounded-full">
|
||||
<i class="ph ph-globe-stand text-white/70 text-[13px]"></i>
|
||||
<span class="text-[11px] uppercase text-white/70 tracking-wide">Mail-DNS Health</span>
|
||||
</div>
|
||||
<div class="text-xs text-white/60">
|
||||
<span class="opacity-70">TLSA:</span>
|
||||
<span class="{{ $tlsa ? 'text-emerald-300' : 'text-rose-300' }}">
|
||||
{{ $tlsa ? 'ok' : 'fehlend' }}
|
||||
</span>
|
||||
<span class="opacity-50">({{ $mtaHost }})</span>
|
||||
<div class="flex items-center gap-3">
|
||||
<button wire:click="refresh"
|
||||
class="inline-flex items-center gap-1.5 rounded-full text-[12px] px-3 py-1.5
|
||||
text-white/80 bg-white/10 border border-white/15
|
||||
hover:bg-white/15 hover:text-white transition">
|
||||
<i class="ph ph-arrows-clockwise text-[13px]"></i>
|
||||
Neu prüfen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -17,7 +19,7 @@
|
|||
@forelse($rows as $r)
|
||||
<button type="button"
|
||||
wire:click="openDnsModal({{ $r['id'] }})"
|
||||
class="w-full text-left py-3 flex items-center justify-between hover:bg-white/5/20 rounded-lg px-2 hover:bg-white/5">
|
||||
class="w-full text-left py-3 flex items-center justify-between rounded-lg px-2 hover:bg-white/5">
|
||||
<div class="text-white/85">{{ $r['name'] }}</div>
|
||||
|
||||
@if($r['ok'])
|
||||
|
|
@ -25,14 +27,9 @@
|
|||
OK
|
||||
</span>
|
||||
@else
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 rounded-full border text-amber-200 border-amber-400/30 bg-amber-500/10 text-xs">
|
||||
Fertig konfigurieren
|
||||
</span>
|
||||
{{-- <span class="text-[11px] text-white/45">--}}
|
||||
{{-- fehlt: {{ implode(', ', $r['missing']) }}--}}
|
||||
{{-- </span>--}}
|
||||
</div>
|
||||
<span class="px-2 py-0.5 rounded-full border text-amber-200 border-amber-400/30 bg-amber-500/10 text-xs">
|
||||
Fertig konfigurieren
|
||||
</span>
|
||||
@endif
|
||||
</button>
|
||||
@empty
|
||||
|
|
@ -41,6 +38,49 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{{--<div wire:poll.60s="refresh" class="glass-card p-5 rounded-2xl border border-white/10 bg-white/5">--}}
|
||||
{{-- <div class="flex items-center justify-between mb-4">--}}
|
||||
{{-- <div class="inline-flex items-center gap-2 bg-white/5 border border-white/10 px-2.5 py-1 rounded-full">--}}
|
||||
{{-- <i class="ph ph-globe-stand text-white/70 text-[13px]"></i>--}}
|
||||
{{-- <span class="text-[11px] uppercase text-white/70 tracking-wide">Mail-DNS Health</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="text-xs text-white/60">--}}
|
||||
{{-- <span class="opacity-70">TLSA:</span>--}}
|
||||
{{-- <span class="{{ $tlsa ? 'text-emerald-300' : 'text-rose-300' }}">--}}
|
||||
{{-- {{ $tlsa ? 'ok' : 'fehlend' }}--}}
|
||||
{{-- </span>--}}
|
||||
{{-- <span class="opacity-50">({{ $mtaHost }})</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
|
||||
{{-- <div class="divide-y divide-white/5">--}}
|
||||
{{-- @forelse($rows as $r)--}}
|
||||
{{-- <button type="button"--}}
|
||||
{{-- wire:click="openDnsModal({{ $r['id'] }})"--}}
|
||||
{{-- class="w-full text-left py-3 flex items-center justify-between hover:bg-white/5/20 rounded-lg px-2 hover:bg-white/5">--}}
|
||||
{{-- <div class="text-white/85">{{ $r['name'] }}</div>--}}
|
||||
|
||||
{{-- @if($r['ok'])--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded-full border text-emerald-300 border-emerald-400/30 bg-emerald-500/10 text-xs">--}}
|
||||
{{-- OK--}}
|
||||
{{-- </span>--}}
|
||||
{{-- @else--}}
|
||||
{{-- <div class="flex items-center gap-2">--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded-full border text-amber-200 border-amber-400/30 bg-amber-500/10 text-xs">--}}
|
||||
{{-- Fertig konfigurieren--}}
|
||||
{{-- </span>--}}
|
||||
{{-- <span class="text-[11px] text-white/45">--}}
|
||||
{{-- fehlt: {{ implode(', ', $r['missing']) }}--}}
|
||||
{{-- </span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @endif--}}
|
||||
{{-- </button>--}}
|
||||
{{-- @empty--}}
|
||||
{{-- <div class="py-4 text-sm text-white/60">Keine Domains.</div>--}}
|
||||
{{-- @endforelse--}}
|
||||
{{-- </div>--}}
|
||||
{{--</div>--}}
|
||||
|
||||
{{--<div wire:poll.60s="refresh" class="glass-card p-5 rounded-2xl border border-white/10 bg-white/5">--}}
|
||||
{{-- <div class="flex items-center justify-between mb-4">--}}
|
||||
{{-- <div class="inline-flex items-center gap-2 bg-white/5 border border-white/10 px-2.5 py-1 rounded-full">--}}
|
||||
|
|
|
|||
Loading…
Reference in New Issue