parent
c8cae445c5
commit
659f3cb7ae
|
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use App\Models\Setting;
|
||||
|
||||
class ProbeRbl extends Command
|
||||
{
|
||||
protected $signature = 'rbl:probe {--force : Ignoriert Intervalle und prüft sofort}';
|
||||
protected $description = 'Prüft öffentliche RBLs und speichert das Ergebnis in settings:health.rbl';
|
||||
|
||||
// Intervalle
|
||||
private int $minIntervalDays = 7; // frühestens alle 7 Tage neu prüfen
|
||||
private int $ttlDays = 14; // Ergebnis 14 Tage gültig
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$now = now();
|
||||
|
||||
$existing = (array) Setting::get('health.rbl', []) ?: [];
|
||||
$lastAt = isset($existing['checked_at']) ? \Illuminate\Support\Carbon::parse($existing['checked_at']) : null;
|
||||
$nextDue = $lastAt ? $lastAt->copy()->addDays($this->minIntervalDays) : null;
|
||||
|
||||
if (!$this->option('force') && $nextDue && $now->lt($nextDue)) {
|
||||
$this->info("Übersprungen: nächste Prüfung erst ab {$nextDue->toIso8601String()} (force mit --force).");
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
// IPs ermitteln (Installer-ENV bevorzugt)
|
||||
[$ipv4, $ipv6] = $this->resolvePublicIpsFromInstallerEnv();
|
||||
$ipv4 = $ipv4 ?: trim((string) env('SERVER_PUBLIC_IPV4', '')) ?: null;
|
||||
$ipv6 = $ipv6 ?: trim((string) env('SERVER_PUBLIC_IPV6', '')) ?: null;
|
||||
|
||||
// Kandidat für RBL (nur IPv4)
|
||||
$ip = $this->validIPv4($ipv4) ? $ipv4 : null;
|
||||
if (!$ip) {
|
||||
$file = trim((string) @file_get_contents('/etc/mailwolt/public_ip'));
|
||||
if ($this->validIPv4($file)) $ip = $file;
|
||||
}
|
||||
if (!$ip) {
|
||||
$curl = trim((string) @shell_exec('curl -fsS --max-time 2 ifconfig.me 2>/dev/null'));
|
||||
if ($this->validIPv4($curl)) $ip = $curl;
|
||||
}
|
||||
if (!$ip) $ip = '0.0.0.0';
|
||||
|
||||
// Abfragen (DNS)
|
||||
[$lists, $meta] = $this->queryRblLists($ip);
|
||||
|
||||
$payload = [
|
||||
'ip' => $ip,
|
||||
'ipv4' => $ipv4,
|
||||
'ipv6' => $ipv6,
|
||||
'hits' => count($lists),
|
||||
'lists' => array_values($lists), // nur die tatsächlich gelisteten Zonen
|
||||
'meta' => $meta, // {zone:{status, txt?}}
|
||||
'checked_at' => $now->toIso8601String(),
|
||||
'valid_until' => $now->copy()->addDays($this->ttlDays)->toIso8601String(),
|
||||
'min_next' => $now->copy()->addDays($this->minIntervalDays)->toIso8601String(),
|
||||
];
|
||||
|
||||
// Persistieren (DB) + in Redis spiegeln
|
||||
Setting::set('health.rbl', $payload);
|
||||
Cache::put('health.rbl', $payload, now()->addDays($this->ttlDays));
|
||||
|
||||
$this->info(sprintf(
|
||||
'RBL: ip=%s hits=%d lists=[%s]',
|
||||
$payload['ip'], $payload['hits'], implode(',', $payload['lists'])
|
||||
));
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/* ---------- Helpers ---------- */
|
||||
|
||||
private function resolvePublicIpsFromInstallerEnv(): array
|
||||
{
|
||||
$file = '/etc/mailwolt/installer.env';
|
||||
if (!is_readable($file)) return [null, null];
|
||||
|
||||
$ipv4 = $ipv6 = null;
|
||||
foreach (@file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [] as $line) {
|
||||
if ($line === '' || $line[0] === '#') continue;
|
||||
if (!str_contains($line, '=')) continue;
|
||||
[$k, $v] = array_map('trim', explode('=', $line, 2));
|
||||
$v = trim($v, " \t\n\r\0\x0B\"'");
|
||||
if ($k === 'SERVER_PUBLIC_IPV4' && $this->validIPv4($v)) $ipv4 = $v;
|
||||
if ($k === 'SERVER_PUBLIC_IPV6' && $this->validIPv6($v)) $ipv6 = $v;
|
||||
}
|
||||
return [ $ipv4, $ipv6 ];
|
||||
}
|
||||
|
||||
private function validIPv4(?string $ip): bool
|
||||
{
|
||||
return (bool) filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
|
||||
}
|
||||
private function validIPv6(?string $ip): bool
|
||||
{
|
||||
return (bool) filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt [listedZones, meta] zurück.
|
||||
* meta[zone] = ['status'=>'listed|clean|blocked|nx', 'txt'=>?string]
|
||||
*/
|
||||
private function queryRblLists(string $ip): array
|
||||
{
|
||||
if (!$this->validIPv4($ip)) return [[], []];
|
||||
|
||||
$rev = implode('.', array_reverse(explode('.', $ip)));
|
||||
|
||||
// Kuratierte, erreichbare Zonen (ohne kaputte Subdomains)
|
||||
$zones = [
|
||||
// Spamhaus ZEN – rate-limitiert, liefert „blocked“ bei Open Resolver
|
||||
'zen.spamhaus.org',
|
||||
// PSBL
|
||||
'psbl.surriel.com',
|
||||
// UCEPROTECT Level 1
|
||||
'dnsbl-1.uceprotect.net',
|
||||
// s5h
|
||||
'bl.s5h.net',
|
||||
];
|
||||
|
||||
$listed = [];
|
||||
$meta = [];
|
||||
|
||||
foreach ($zones as $zone) {
|
||||
$q = "{$rev}.{$zone}.";
|
||||
|
||||
$txt = @dns_get_record($q, DNS_TXT) ?: [];
|
||||
$a = @dns_get_record($q, DNS_A) ?: [];
|
||||
|
||||
// Spamhaus „blocked“ Heuristik
|
||||
$blocked = false;
|
||||
if ($zone === 'zen.spamhaus.org') {
|
||||
foreach ($a as $rec) {
|
||||
if (!empty($rec['ip']) && in_array($rec['ip'], ['127.255.255.254','127.255.255.255'], true)) {
|
||||
$blocked = true; break;
|
||||
}
|
||||
}
|
||||
if (!$blocked) {
|
||||
foreach ($txt as $rec) {
|
||||
$t = implode('', $rec['txt'] ?? []);
|
||||
if (stripos($t, 'open resolver') !== false) { $blocked = true; break; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($blocked) {
|
||||
$meta[$zone] = ['status' => 'blocked', 'txt' => 'Spamhaus blockt – nutze privaten Resolver'];
|
||||
continue;
|
||||
}
|
||||
|
||||
$hasA = !empty($a);
|
||||
$hasTXT = !empty($txt);
|
||||
if ($hasA || $hasTXT) {
|
||||
$listed[] = $zone;
|
||||
$meta[$zone] = ['status' => 'listed', 'txt' => $hasTXT ? ($txt[0]['txt'][0] ?? null) : null];
|
||||
} else {
|
||||
// NXDOMAIN / sauber
|
||||
$meta[$zone] = ['status' => 'clean', 'txt' => null];
|
||||
}
|
||||
}
|
||||
|
||||
return [$listed, $meta];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
|
|
@ -2,42 +2,339 @@
|
|||
|
||||
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 = []; // [ ['domain'=>..., 'dkim'=>bool, 'dmarc'=>bool, 'tlsa'=>bool], ... ]
|
||||
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); }
|
||||
|
||||
protected function load(bool $force=false): void
|
||||
public function mount(): void
|
||||
{
|
||||
$this->rows = Cache::remember('dash.dnshealth', $force ? 1 : 600, function () {
|
||||
$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)->get(['domain']);
|
||||
$domains = Domain::query()
|
||||
->where('is_system', false)
|
||||
->where('is_active', true)
|
||||
->orderBy('domain')
|
||||
->get(['id', 'domain']);
|
||||
|
||||
foreach ($domains as $d) {
|
||||
$dom = $d->domain;
|
||||
$dkim = $this->hasTxt("_domainkey.$dom"); // rough: just any dkim TXT exists
|
||||
$dmarc = $this->hasTxt("_dmarc.$dom");
|
||||
$tlsa = $this->hasTlsa("_25._tcp.$dom") || $this->hasTlsa("_465._tcp.$dom") || $this->hasTlsa("_587._tcp.$dom");
|
||||
$rows[] = compact('dom','dkim','dmarc','tlsa');
|
||||
|
||||
$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,
|
||||
];
|
||||
}
|
||||
return $rows;
|
||||
|
||||
// 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
|
||||
{
|
||||
$out = @shell_exec("dig +short TXT ".escapeshellarg($name)." 2>/dev/null");
|
||||
return is_string($out) && trim($out) !== '';
|
||||
return trim($this->digShort('TXT', $name)) !== '';
|
||||
}
|
||||
|
||||
protected function hasTlsa(string $name): bool
|
||||
{
|
||||
$out = @shell_exec("dig +short TLSA ".escapeshellarg($name)." 2>/dev/null");
|
||||
return is_string($out) && trim($out) !== '';
|
||||
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;
|
||||
//use App\Models\Domain;
|
||||
//use Illuminate\Support\Facades\Cache;
|
||||
//
|
||||
//class DnsHealthCard extends Component
|
||||
//{
|
||||
// public array $domains = []; // [['name'=>..., 'dkim'=>bool, 'dmarc'=>bool], ...]
|
||||
// public string $host = ''; // z.B. mx.nexlab.at
|
||||
// public bool $tlsa = false; // hostbasiert (einmalig)
|
||||
// public ?string $ipv4 = null;
|
||||
// public ?string $ipv6 = null;
|
||||
//
|
||||
// public function mount(): void
|
||||
// {
|
||||
// $this->load();
|
||||
// }
|
||||
//
|
||||
// public function render()
|
||||
// {
|
||||
// return view('livewire.ui.mail.dns-health-card');
|
||||
// }
|
||||
//
|
||||
// public function refresh(): void
|
||||
// {
|
||||
// $this->load(true);
|
||||
// }
|
||||
//
|
||||
// protected function load(bool $force = false): void
|
||||
// {
|
||||
// [$this->host, $this->tlsa, $this->domains, $this->ipv4, $this->ipv6] =
|
||||
// Cache::remember('dash.dnshealth', $force ? 1 : 900, function () {
|
||||
//
|
||||
// // ── ENV lesen ────────────────────────────────────────────────
|
||||
// $base = trim((string)env('BASE_DOMAIN', ''));
|
||||
// $mtaSub = trim((string)env('MTA_SUB', 'mx'));
|
||||
// $host = $base !== '' ? "{$mtaSub}.{$base}" : $mtaSub;
|
||||
//
|
||||
// $ipv4 = trim((string)env('SERVER_PUBLIC_IPV4', '')) ?: null;
|
||||
// $ipv6 = trim((string)env('SERVER_PUBLIC_IPV6', '')) ?: null;
|
||||
//
|
||||
// // ── Domains laden (nur aktive, nicht-system) ────────────────
|
||||
// $rows = [];
|
||||
// $domains = Domain::query()
|
||||
// ->where('is_system', false)
|
||||
// ->where('is_active', true)
|
||||
// ->orderBy('domain')
|
||||
// ->get(['domain']);
|
||||
//
|
||||
// foreach ($domains as $d) {
|
||||
// $dom = $d->domain;
|
||||
// $rows[] = [
|
||||
// 'name' => $dom,
|
||||
// 'dkim' => $this->hasTxt("_domainkey.$dom"),
|
||||
// 'dmarc' => $this->hasTxt("_dmarc.$dom"),
|
||||
// ];
|
||||
// }
|
||||
//
|
||||
// // ── TLSA nur hostbasiert prüfen (25/465/587) ────────────────
|
||||
// $tlsa = $this->hasTlsa("_25._tcp.$host")
|
||||
// || $this->hasTlsa("_465._tcp.$host")
|
||||
// || $this->hasTlsa("_587._tcp.$host");
|
||||
//
|
||||
// return [$host, $tlsa, $rows, $ipv4, $ipv6];
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// /* ───────────────────────── DNS Helpers ───────────────────────── */
|
||||
//
|
||||
// protected function hasTxt(string $name): bool
|
||||
// {
|
||||
// $out = @shell_exec("timeout 2 dig +short TXT " . escapeshellarg($name) . " 2>/dev/null");
|
||||
// return is_string($out) && trim($out) !== '';
|
||||
// }
|
||||
//
|
||||
// protected function hasTlsa(string $name): bool
|
||||
// {
|
||||
// $out = @shell_exec("timeout 2 dig +short TLSA " . escapeshellarg($name) . " 2>/dev/null");
|
||||
// return is_string($out) && trim($out) !== '';
|
||||
// }
|
||||
//}
|
||||
|
||||
//namespace App\Livewire\Ui\Mail;
|
||||
//
|
||||
//use Livewire\Component;
|
||||
//use App\Models\Domain;
|
||||
//use Illuminate\Support\Facades\Cache;
|
||||
//
|
||||
//class DnsHealthCard extends Component
|
||||
//{
|
||||
// public array $rows = []; // pro Domain: ['dom','dkim','dmarc']
|
||||
// public string $host = ''; // z.B. mx.nexlab.at
|
||||
// public bool $tlsa = false; // TLSA-Status für den Host
|
||||
// public ?string $ipv4 = null;
|
||||
// public ?string $ipv6 = null;
|
||||
//
|
||||
// public function mount(): void { $this->load(); }
|
||||
// public function render() { return view('livewire.ui.mail.dns-health-card'); }
|
||||
// public function refresh(): void { $this->load(true); }
|
||||
//
|
||||
// protected function load(bool $force = false): void
|
||||
// {
|
||||
// // Werte aus .env
|
||||
// $this->ipv4 = trim((string) env('SERVER_PUBLIC_IPV4', '')) ?: null;
|
||||
// $this->ipv6 = trim((string) env('SERVER_PUBLIC_IPV6', '')) ?: null;
|
||||
//
|
||||
// $base = trim((string) env('BASE_DOMAIN', ''));
|
||||
// $mta = trim((string) env('MTA_SUB', 'mx'));
|
||||
// $host = $base ? "{$mta}.{$base}" : $mta; // z.B. mx.nexlab.at
|
||||
// $this->host = $host;
|
||||
//
|
||||
// // Neuer Cache-Key, damit altes Format keinen Crash verursacht
|
||||
// [$calcHost, $calcTlsa, $calcRows] = Cache::remember(
|
||||
// 'dash.dnshealth.v2',
|
||||
// $force ? 1 : 900,
|
||||
// function () use ($host) {
|
||||
// // TLSA: nur 1× pro Host prüfen
|
||||
// $tlsa = $this->hasTlsa("_25._tcp.{$host}")
|
||||
// || $this->hasTlsa("_465._tcp.{$host}")
|
||||
// || $this->hasTlsa("_587._tcp.{$host}");
|
||||
//
|
||||
// // Domains: nur DKIM/DMARC
|
||||
// $rows = [];
|
||||
// $domains = Domain::query()
|
||||
// ->where('is_system', false)
|
||||
// ->where('is_active', true)
|
||||
// ->get(['domain']);
|
||||
//
|
||||
// foreach ($domains as $d) {
|
||||
// $dom = $d->domain;
|
||||
// $dkim = $this->hasTxt("_domainkey.{$dom}");
|
||||
// $dmarc = $this->hasTxt("_dmarc.{$dom}");
|
||||
// $rows[] = compact('dom','dkim','dmarc');
|
||||
// }
|
||||
//
|
||||
// return [$host, $tlsa, $rows];
|
||||
// }
|
||||
// );
|
||||
//
|
||||
// // Defensive: falls mal falsches Datenformat im Cache war
|
||||
// if (!is_string($calcHost) || !is_bool($calcTlsa) || !is_array($calcRows)) {
|
||||
// Cache::forget('dash.dnshealth.v2');
|
||||
// $this->load(true);
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// $this->host = $calcHost;
|
||||
// $this->tlsa = $calcTlsa;
|
||||
// $this->rows = $calcRows;
|
||||
// }
|
||||
//
|
||||
// protected function hasTxt(string $name): bool
|
||||
// {
|
||||
// $out = @shell_exec("dig +short TXT " . escapeshellarg($name) . " 2>/dev/null");
|
||||
// return is_string($out) && trim($out) !== '';
|
||||
// }
|
||||
//
|
||||
// protected function hasTlsa(string $name): bool
|
||||
// {
|
||||
// $out = @shell_exec("dig +short TLSA " . escapeshellarg($name) . " 2>/dev/null");
|
||||
// return is_string($out) && trim($out) !== '';
|
||||
// }
|
||||
//}
|
||||
|
||||
//
|
||||
//namespace App\Livewire\Ui\Mail;
|
||||
//
|
||||
//use Livewire\Component;
|
||||
//use App\Models\Domain;
|
||||
//use Illuminate\Support\Facades\Cache;
|
||||
//
|
||||
//class DnsHealthCard extends Component
|
||||
//{
|
||||
// public array $rows = []; // [ ['domain'=>..., 'dkim'=>bool, 'dmarc'=>bool, 'tlsa'=>bool], ... ]
|
||||
//
|
||||
// public function mount(): void { $this->load(); }
|
||||
// public function render() { return view('livewire.ui.mail.dns-health-card'); }
|
||||
// public function refresh(): void { $this->load(true); }
|
||||
//
|
||||
// protected function load(bool $force=false): void
|
||||
// {
|
||||
// $this->rows = Cache::remember('dash.dnshealth', $force ? 1 : 600, function () {
|
||||
// $rows = [];
|
||||
// $domains = Domain::query()->where('is_system', false)->where('is_active', true)->get(['domain']);
|
||||
// foreach ($domains as $d) {
|
||||
// $dom = $d->domain;
|
||||
// $dkim = $this->hasTxt("_domainkey.$dom"); // rough: just any dkim TXT exists
|
||||
// $dmarc = $this->hasTxt("_dmarc.$dom");
|
||||
// $tlsa = $this->hasTlsa("_25._tcp.$dom") || $this->hasTlsa("_465._tcp.$dom") || $this->hasTlsa("_587._tcp.$dom");
|
||||
// $rows[] = compact('dom','dkim','dmarc','tlsa');
|
||||
// }
|
||||
// return $rows;
|
||||
// });
|
||||
// }
|
||||
//
|
||||
// protected function hasTxt(string $name): bool
|
||||
// {
|
||||
// $out = @shell_exec("dig +short TXT ".escapeshellarg($name)." 2>/dev/null");
|
||||
// return is_string($out) && trim($out) !== '';
|
||||
// }
|
||||
// protected function hasTlsa(string $name): bool
|
||||
// {
|
||||
// $out = @shell_exec("dig +short TLSA ".escapeshellarg($name)." 2>/dev/null");
|
||||
// return is_string($out) && trim($out) !== '';
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -1,142 +1,192 @@
|
|||
<?php
|
||||
|
||||
|
||||
// App\Livewire\Ui\Security\RblCard.php
|
||||
namespace App\Livewire\Ui\Security;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Livewire\Component;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Support\Facades\Artisan;
|
||||
use App\Models\Setting;
|
||||
|
||||
class RblCard extends Component
|
||||
{
|
||||
public string $ip = '–';
|
||||
public int $hits = 0;
|
||||
public array $lists = [];
|
||||
|
||||
public string $ip = '–';
|
||||
public ?string $ipv4 = null;
|
||||
public ?string $ipv6 = null;
|
||||
|
||||
// Schalte registrierungspflichtige Listen (Barracuda etc.) optional zu
|
||||
private bool $includeRegistered = false; // env('RBL_INCLUDE_REGISTERED', false) wenn du willst
|
||||
public int $hits = 0;
|
||||
public array $lists = []; // nur gelistete Zonen
|
||||
public array $meta = []; // status je Zone
|
||||
public ?string $checkedAt = null;
|
||||
public ?string $validUntil = null;
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
$this->load();
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.ui.security.rbl-card');
|
||||
}
|
||||
public function mount(): void { $this->load(); }
|
||||
public function render() { return view('livewire.ui.security.rbl-card'); }
|
||||
|
||||
public function refresh(): void
|
||||
{
|
||||
Cache::forget('dash.rbl');
|
||||
// Manuelles Re-Check via Command (asynchron, damit UI nicht blockiert)
|
||||
@shell_exec('nohup php /var/www/mailwolt/artisan rbl:probe --force >/dev/null 2>&1 &');
|
||||
// Sofortige UI-Aktualisierung aus Settings (altes Ergebnis) …
|
||||
$this->load(true);
|
||||
// … und kurzer Hinweis
|
||||
$this->dispatch('toast', type:'info', title:'RBL-Prüfung gestartet', text:'Ergebnis wird aktualisiert, sobald verfügbar.', duration:2500);
|
||||
}
|
||||
|
||||
protected function load(bool $force = false): void
|
||||
{
|
||||
[$ip4, $ip6] = $this->resolvePublicIpsFromInstallerEnv();
|
||||
$payload = $force
|
||||
? (array) Setting::get('health.rbl', []) // direkt aus DB
|
||||
: (array) (Cache::get('health.rbl') ?: Setting::get('health.rbl', []));
|
||||
|
||||
$this->ipv4 = $ip4 ?: trim((string)env('SERVER_PUBLIC_IPV4', '')) ?: '–';
|
||||
$this->ipv6 = $ip6 ?: trim((string)env('SERVER_PUBLIC_IPV6', '')) ?: '–';
|
||||
|
||||
$data = Cache::remember('dash.rbl', $force ? 1 : 6 * 3600, function () {
|
||||
$candidate = $this->validIPv4($this->ipv4 ?? '') ? $this->ipv4 : null;
|
||||
|
||||
if (!$candidate) {
|
||||
$fromFile = trim((string)@file_get_contents('/etc/mailwolt/public_ip'));
|
||||
if ($this->validIPv4($fromFile)) $candidate = $fromFile;
|
||||
}
|
||||
if (!$candidate) {
|
||||
$curl = trim((string)@shell_exec("curl -fsS --max-time 2 ifconfig.me 2>/dev/null"));
|
||||
if ($this->validIPv4($curl)) $candidate = $curl;
|
||||
}
|
||||
|
||||
$ip = $candidate ?: '0.0.0.0';
|
||||
$lists = $this->queryRblLists($ip);
|
||||
|
||||
return ['ip' => $ip, 'hits' => count($lists), 'lists' => $lists];
|
||||
});
|
||||
|
||||
foreach ($data as $k => $v) $this->$k = $v;
|
||||
}
|
||||
|
||||
/** bevorzugt Installer-ENV */
|
||||
private function resolvePublicIpsFromInstallerEnv(): array
|
||||
{
|
||||
$file = '/etc/mailwolt/installer.env';
|
||||
if (!is_readable($file)) return [null, null];
|
||||
|
||||
$ipv4 = $ipv6 = null;
|
||||
$lines = @file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||
foreach ($lines as $line) {
|
||||
if (preg_match('/^\s*#/', $line) || !str_contains($line, '=')) continue;
|
||||
[$k, $v] = array_map('trim', explode('=', $line, 2));
|
||||
$v = trim($v, " \t\n\r\0\x0B\"'");
|
||||
if ($k === 'SERVER_PUBLIC_IPV4' && $this->validIPv4($v)) $ipv4 = $v;
|
||||
if ($k === 'SERVER_PUBLIC_IPV6' && $this->validIPv6($v)) $ipv6 = $v;
|
||||
}
|
||||
return [$ipv4, $ipv6];
|
||||
}
|
||||
|
||||
private function validIPv4(?string $ip): bool
|
||||
{
|
||||
return (bool)filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
|
||||
}
|
||||
|
||||
private function validIPv6(?string $ip): bool
|
||||
{
|
||||
return (bool)filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prüft die IP gegen gängige **öffentliche** RBLs.
|
||||
* @return array<string> gelistete RBL-Zonen
|
||||
*/
|
||||
private function queryRblLists(string $ip): array
|
||||
{
|
||||
if (!$this->validIPv4($ip)) return [];
|
||||
|
||||
$rev = implode('.', array_reverse(explode('.', $ip)));
|
||||
|
||||
// Öffentliche/abfragbare Zonen (keine Registrierung nötig)
|
||||
$publicZones = [
|
||||
'zen.spamhaus.org', // seriös, rate-limited
|
||||
'psbl.surriel.com', // public
|
||||
'dnsbl-1.uceprotect.net', // public (umstritten, aber abfragbar)
|
||||
'all.s5h.net', // public
|
||||
];
|
||||
|
||||
// Registrierungspflichtige – nur optional prüfen
|
||||
$registeredZones = [
|
||||
'b.barracudacentral.org', // benötigt Account
|
||||
// weitere bei Bedarf
|
||||
];
|
||||
|
||||
$zones = $publicZones;
|
||||
if ($this->includeRegistered) {
|
||||
$zones = array_merge($zones, $registeredZones);
|
||||
}
|
||||
|
||||
// Vorab: Zone existiert? (NS-Record) – sonst überspringen
|
||||
$zones = array_values(array_filter($zones, function ($z) {
|
||||
return @checkdnsrr($z . '.', 'NS');
|
||||
}));
|
||||
|
||||
$listed = [];
|
||||
foreach ($zones as $zone) {
|
||||
$qname = "{$rev}.{$zone}.";
|
||||
// Wenn A oder TXT existiert → gelistet
|
||||
if (@checkdnsrr($qname, 'A') || @checkdnsrr($qname, 'TXT')) {
|
||||
$listed[] = $zone;
|
||||
}
|
||||
}
|
||||
|
||||
return $listed;
|
||||
$this->ip = (string)($payload['ip'] ?? '–');
|
||||
$this->ipv4 = $payload['ipv4'] ?? null;
|
||||
$this->ipv6 = $payload['ipv6'] ?? null;
|
||||
$this->hits = (int)($payload['hits'] ?? 0);
|
||||
$this->lists = (array)($payload['lists'] ?? []);
|
||||
$this->meta = (array)($payload['meta'] ?? []);
|
||||
$this->checkedAt = $payload['checked_at'] ?? null;
|
||||
$this->validUntil = $payload['valid_until'] ?? null;
|
||||
}
|
||||
}
|
||||
//declare(strict_types=1);
|
||||
|
||||
//namespace App\Livewire\Ui\Security;
|
||||
//
|
||||
//use Illuminate\Support\Facades\Cache;
|
||||
//use Livewire\Component;
|
||||
//
|
||||
//class RblCard extends Component
|
||||
//{
|
||||
// public string $ip = '–';
|
||||
// public int $hits = 0;
|
||||
// public array $lists = [];
|
||||
//
|
||||
// public ?string $ipv4 = null;
|
||||
// public ?string $ipv6 = null;
|
||||
//
|
||||
// // Schalte registrierungspflichtige Listen (Barracuda etc.) optional zu
|
||||
// private bool $includeRegistered = false; // env('RBL_INCLUDE_REGISTERED', false) wenn du willst
|
||||
//
|
||||
// public function mount(): void
|
||||
// {
|
||||
// $this->load();
|
||||
// }
|
||||
//
|
||||
// public function render()
|
||||
// {
|
||||
// return view('livewire.ui.security.rbl-card');
|
||||
// }
|
||||
//
|
||||
// public function refresh(): void
|
||||
// {
|
||||
// Cache::forget('dash.rbl');
|
||||
// $this->load(true);
|
||||
// }
|
||||
//
|
||||
// protected function load(bool $force = false): void
|
||||
// {
|
||||
// [$ip4, $ip6] = $this->resolvePublicIpsFromInstallerEnv();
|
||||
//
|
||||
// $this->ipv4 = $ip4 ?: trim((string)env('SERVER_PUBLIC_IPV4', '')) ?: '–';
|
||||
// $this->ipv6 = $ip6 ?: trim((string)env('SERVER_PUBLIC_IPV6', '')) ?: '–';
|
||||
//
|
||||
// $data = Cache::remember('dash.rbl', $force ? 1 : 6 * 3600, function () {
|
||||
// $candidate = $this->validIPv4($this->ipv4 ?? '') ? $this->ipv4 : null;
|
||||
//
|
||||
// if (!$candidate) {
|
||||
// $fromFile = trim((string)@file_get_contents('/etc/mailwolt/public_ip'));
|
||||
// if ($this->validIPv4($fromFile)) $candidate = $fromFile;
|
||||
// }
|
||||
// if (!$candidate) {
|
||||
// $curl = trim((string)@shell_exec("curl -fsS --max-time 2 ifconfig.me 2>/dev/null"));
|
||||
// if ($this->validIPv4($curl)) $candidate = $curl;
|
||||
// }
|
||||
//
|
||||
// $ip = $candidate ?: '0.0.0.0';
|
||||
// $lists = $this->queryRblLists($ip);
|
||||
//
|
||||
// return ['ip' => $ip, 'hits' => count($lists), 'lists' => $lists];
|
||||
// });
|
||||
//
|
||||
// foreach ($data as $k => $v) $this->$k = $v;
|
||||
// }
|
||||
//
|
||||
// /** bevorzugt Installer-ENV */
|
||||
// private function resolvePublicIpsFromInstallerEnv(): array
|
||||
// {
|
||||
// $file = '/etc/mailwolt/installer.env';
|
||||
// if (!is_readable($file)) return [null, null];
|
||||
//
|
||||
// $ipv4 = $ipv6 = null;
|
||||
// $lines = @file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES) ?: [];
|
||||
// foreach ($lines as $line) {
|
||||
// if (preg_match('/^\s*#/', $line) || !str_contains($line, '=')) continue;
|
||||
// [$k, $v] = array_map('trim', explode('=', $line, 2));
|
||||
// $v = trim($v, " \t\n\r\0\x0B\"'");
|
||||
// if ($k === 'SERVER_PUBLIC_IPV4' && $this->validIPv4($v)) $ipv4 = $v;
|
||||
// if ($k === 'SERVER_PUBLIC_IPV6' && $this->validIPv6($v)) $ipv6 = $v;
|
||||
// }
|
||||
// return [$ipv4, $ipv6];
|
||||
// }
|
||||
//
|
||||
// private function validIPv4(?string $ip): bool
|
||||
// {
|
||||
// return (bool)filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
|
||||
// }
|
||||
//
|
||||
// private function validIPv6(?string $ip): bool
|
||||
// {
|
||||
// return (bool)filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
|
||||
// }
|
||||
//
|
||||
// /**
|
||||
// * Prüft die IP gegen gängige **öffentliche** RBLs.
|
||||
// * @return array<string> gelistete RBL-Zonen
|
||||
// */
|
||||
// private function queryRblLists(string $ip): array
|
||||
// {
|
||||
// if (!$this->validIPv4($ip)) return [];
|
||||
//
|
||||
// $rev = implode('.', array_reverse(explode('.', $ip)));
|
||||
//
|
||||
// // nur Zonen prüfen, die es wirklich gibt
|
||||
// $zones = [
|
||||
// 'zen.spamhaus.org',
|
||||
// 'psbl.surriel.com',
|
||||
// 'dnsbl-1.uceprotect.net',
|
||||
// 'all.s5h.net',
|
||||
// ];
|
||||
// $zones = array_values(array_filter($zones, fn($z) => @checkdnsrr($z.'.','NS')));
|
||||
//
|
||||
// $listed = [];
|
||||
// foreach ($zones as $zone) {
|
||||
// $q = "{$rev}.{$zone}.";
|
||||
//
|
||||
// $a = @dns_get_record($q, DNS_A) ?: [];
|
||||
// if (!count($a)) continue;
|
||||
//
|
||||
// $ips = array_column($a, 'ip');
|
||||
//
|
||||
// // --- WICHTIG: Spamhaus "blocked" / Ratelimit ignorieren
|
||||
// if (array_intersect($ips, ['127.255.255.254','127.255.255.255'])) {
|
||||
// // optional: merk dir, dass Spamhaus blockt -> UI-Hinweis
|
||||
// $listed[] = ['zone'=>$zone, 'code'=>'blocked', 'txt'=>null];
|
||||
// continue;
|
||||
// }
|
||||
//
|
||||
// $txtRecs = @dns_get_record($q, DNS_TXT) ?: [];
|
||||
// $txt = $txtRecs[0]['txt'] ?? null;
|
||||
//
|
||||
// $listed[] = ['zone'=>$zone, 'code'=>$ips[0] ?? null, 'txt'=>$txt];
|
||||
// }
|
||||
//
|
||||
// // Nur echte Treffer zurückgeben; „blocked“ separat signalisieren
|
||||
// $real = array_values(array_filter($listed, fn($e) => ($e['code'] ?? null) !== 'blocked'));
|
||||
//
|
||||
// // Falls alles nur "blocked" war, gib leere Liste zurück
|
||||
// return array_map(fn($e) => $e['zone'].($e['code'] ? " ({$e['code']})" : ''), $real);
|
||||
// }
|
||||
//}
|
||||
////declare(strict_types=1);
|
||||
//
|
||||
//namespace App\Livewire\Ui\Security;
|
||||
//
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ class UpdateCard extends Component
|
|||
@shell_exec('nohup php /var/www/mailwolt/artisan health:collect >/dev/null 2>&1 &');
|
||||
@shell_exec('nohup php /var/www/mailwolt/artisan settings:sync >/dev/null 2>&1 &');
|
||||
@shell_exec('nohup php /var/www/mailwolt/artisan spamav:collect >/dev/null 2>&1 &');
|
||||
@shell_exec('nohup php /var/www/mailwolt/artisan rbl:probe --force >/dev/null 2>&1 &');
|
||||
$this->postActionsDone = true;
|
||||
|
||||
$ver = $this->displayCurrent ?? 'aktuelle Version';
|
||||
|
|
|
|||
|
|
@ -1,14 +1,13 @@
|
|||
{{-- resources/views/livewire/domains/dns-assistant-modal.blade.php --}}
|
||||
@push('modal.header')
|
||||
<div class="px-5 pt-5 pb-3 border-b border-white/10
|
||||
backdrop-blur rounded-t-2xl relative">
|
||||
<span class="absolute top-3 right-3 z-20 inline-flex items-center gap-2
|
||||
rounded-full border border-slate-700/70 bg-slate-800/70
|
||||
px-3 py-1 text-[11px] text-slate-200 shadow-sm">
|
||||
<i class="ph ph-clock text-slate-300"></i> TTL: {{ $ttl }}
|
||||
</span>
|
||||
<h2 class="text-[18px] font-semibold text-slate-100">DNS-Einträge</h2>
|
||||
<div class="px-5 pt-5 pb-3 border-b border-white/10 backdrop-blur rounded-t-2xl relative">
|
||||
<span class="absolute top-3 right-3 z-20 inline-flex items-center gap-2
|
||||
rounded-full border border-slate-700/70 bg-slate-800/70
|
||||
px-3 py-1 text-[11px] text-slate-200 shadow-sm">
|
||||
<i class="ph ph-clock text-slate-300"></i> TTL: {{ $ttl }}
|
||||
</span>
|
||||
|
||||
<h2 class="text-[18px] font-semibold text-slate-100">DNS-Einträge</h2>
|
||||
<p class="text-[13px] text-slate-300/80">
|
||||
Setze die folgenden Records für
|
||||
<span class="text-sky-300 underline decoration-sky-500/40 underline-offset-2">{{ $zone }}</span>.
|
||||
|
|
@ -17,131 +16,366 @@
|
|||
@endpush
|
||||
|
||||
<div class="relative p-5">
|
||||
<div class="space-y-5">
|
||||
{{-- GRID: links (Mail-Records), rechts (Infrastruktur) --}}
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5">
|
||||
{{-- Mail-Records --}}
|
||||
<section>
|
||||
<div class="mb-2 flex items-center gap-2 text-[11px]">
|
||||
<span class="px-2 py-0.5 rounded bg-slate-800/80 text-slate-200">Step 1</span>
|
||||
<span class="text-slate-300/80">Mail-Records</span>
|
||||
<span class="ml-auto px-2 py-0.5 rounded bg-indigo-600/20 text-indigo-200 border border-indigo-500/20">
|
||||
<i class="ph ph-seal-check mr-1"></i>Absenderdomain
|
||||
</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-5">
|
||||
{{-- Step 1: Mail-Records (domain-spezifisch) --}}
|
||||
<section>
|
||||
<div class="mb-2 flex items-center gap-2 text-[11px]">
|
||||
<span class="px-2 py-0.5 rounded bg-slate-800/80 text-slate-200">Step 1</span>
|
||||
<span class="text-slate-300/80">Mail-Records</span>
|
||||
<span class="ml-auto px-2 py-0.5 rounded bg-indigo-600/20 text-indigo-200 border border-indigo-500/20">
|
||||
<i class="ph ph-seal-check mr-1"></i> Absenderdomain
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
@foreach ($dynamic as $r)
|
||||
<div class="rounded-xl border border-white/10 bg-white/[0.04] #rounded-xl #border #border-slate-700/50 #bg-slate-900/60">
|
||||
<div class="flex items-center justify-between px-4 py-2 text-[12px]">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 rounded {{ $recordColors[$r['type']] ?? 'bg-slate-700/50 text-slate-300' }}">{{ $r['type'] }}</span>
|
||||
<span class="text-slate-200">{{ $r['name'] }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-slate-300/70">
|
||||
<x-button.copy-btn :text="$r['value']" />
|
||||
</div>
|
||||
<div class="space-y-4">
|
||||
@foreach ($dynamic as $r)
|
||||
<div class="rounded-xl border {{ $r['boxClass'] ?? $stateColors['neutral'] }}">
|
||||
<div class="flex items-center justify-between px-4 py-2 text-[12px]">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 rounded {{ $recordColors[$r['type']] ?? 'bg-slate-700/50 text-slate-300' }}">
|
||||
{{ $r['type'] }}
|
||||
</span>
|
||||
<span class="text-slate-200">{{ $r['name'] }}</span>
|
||||
</div>
|
||||
<div class="px-4 pb-3">
|
||||
<pre class="text-[12px] w-full rounded-lg bg-white/5 border border-white/10 text-white px-3 py-2 text-sm opacity-70 whitespace-pre-wrap break-all">{{ $r['value'] }}</pre>
|
||||
@if(!empty($r['helpUrl']))
|
||||
<a href="{{ $r['helpUrl'] }}" target="_blank" rel="noopener"
|
||||
class="mt-2 inline-flex items-center gap-1 text-[12px] text-sky-300 hover:text-sky-200">
|
||||
<i class="ph ph-arrow-square-out"></i>{{ $r['helpLabel'] }}
|
||||
</a>
|
||||
@endif
|
||||
<div class="flex items-center gap-2 text-slate-300/70">
|
||||
<x-button.copy-btn :text="$r['value']" />
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
|
||||
@foreach ($optional as $r)
|
||||
<div class="rounded-xl border border-white/10 bg-white/[0.04] #rounded-xl #border #border-slate-700/50 #bg-slate-900/60">
|
||||
<div class="flex items-center justify-between px-4 py-2 text-[12px]">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 rounded {{ $recordColors[$r['type']] ?? 'bg-slate-700/50 text-slate-300' }}">{{ $r['type'] }}</span>
|
||||
<span class="text-slate-200">{{ $r['name'] }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-slate-300/70">
|
||||
<span class="text-[11px] px-2 py-0.5 rounded {{ $recordColors['OPTIONAL'] ?? 'bg-slate-700/50 text-slate-300' }}">Optional</span>
|
||||
<x-button.copy-btn :text="$r['value']" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 pb-3">
|
||||
<pre class="text-[12px] w-full rounded-lg bg-white/5 border border-white/10 text-white px-3 py-2 text-sm opacity-70 whitespace-pre-wrap break-all">{{ $r['value'] }}</pre>
|
||||
@if(!empty($r['helpUrl']))
|
||||
<a href="{{ $r['helpUrl'] }}" target="_blank" rel="noopener"
|
||||
class="mt-2 inline-flex items-center gap-1 text-[12px] text-sky-300 hover:text-sky-200">
|
||||
<i class="ph ph-arrow-square-out"></i>{{ $r['helpLabel'] }}
|
||||
</a>
|
||||
@endif
|
||||
<div class="px-4 pb-3 space-y-2">
|
||||
<pre class="text-[12px] w-full rounded-lg bg-white/5 border border-white/10 text-white px-3 py-2 opacity-80 whitespace-pre-wrap break-all">{{ $r['value'] }}</pre>
|
||||
|
||||
@if($checked && filled(trim($r['actual'] ?? '')) && ($r['state'] ?? '') !== 'ok')
|
||||
<div class="text-[11px] text-white/60 break-words">
|
||||
<span class="opacity-70">Ist:</span>
|
||||
<span class="font-mono break-all">
|
||||
{{ str_replace('"', '', preg_replace('/\s+/', ' ', trim($r['actual']))) }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
{{-- @if($checked && !empty($r['actual']))--}}
|
||||
{{-- <div class="text-[11px] text-white/60">--}}
|
||||
{{-- <span class="opacity-70">Ist:</span>--}}
|
||||
{{-- <span class="font-mono break-words">{{ $r['actual'] }}</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @endif--}}
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- Step 2: Globale Infrastruktur (MTA-Host) --}}
|
||||
<section>
|
||||
<div class="mb-2 flex items-center gap-2 text-[11px]">
|
||||
<span class="px-2 py-0.5 rounded bg-slate-800/80 text-slate-200">Step 2</span>
|
||||
<span class="text-slate-300/80">Globale Infrastruktur (MTA-Host)</span>
|
||||
<span class="text-slate-400/70">gilt für alle Domains</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
@foreach ($static as $r)
|
||||
<div class="rounded-xl border {{ $r['boxClass'] ?? $stateColors['neutral'] }}">
|
||||
<div class="flex items-center justify-between px-4 py-2 text-[12px]">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 rounded {{ $recordColors[$r['type']] ?? 'bg-slate-700/50 text-slate-300' }}">
|
||||
{{ $r['type'] }}
|
||||
</span>
|
||||
<span class="text-slate-200">{{ $r['name'] }}</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{{-- Globale Infrastruktur --}}
|
||||
<section>
|
||||
<div class="mb-2 flex items-center gap-2 text-[11px]">
|
||||
<span class="px-2 py-0.5 rounded bg-slate-800/80 text-slate-200">Step 2</span>
|
||||
<span class="text-slate-300/80">Globale Infrastruktur (MTA-Host)</span>
|
||||
<span class="text-slate-400/70">gilt für alle Domains</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-4">
|
||||
@foreach ($static as $r)
|
||||
<div class="rounded-xl border border-white/10 bg-white/[0.04] #rounded-xl #border #border-slate-700/50 #bg-slate-900/60">
|
||||
<div class="flex items-center justify-between px-4 py-2 text-[12px]">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 rounded {{ $recordColors[$r['type']] ?? 'bg-slate-700/50 text-slate-300' }}">{{ $r['type'] }}</span>
|
||||
<span class="text-slate-200">{{ $r['name'] }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-slate-300/70">
|
||||
<x-button.copy-btn :text="$r['value']" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="px-4 pb-3">
|
||||
<pre class="text-[12px] w-full rounded-lg bg-white/5 border border-white/10 text-white px-3 py-2 text-sm opacity-70 whitespace-pre-wrap break-all">{{ $r['value'] }}</pre>
|
||||
<div class="flex items-center gap-2 text-slate-300/70">
|
||||
<x-button.copy-btn :text="$r['value']" />
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<div class="px-4 pb-3 space-y-2">
|
||||
<pre class="text-[12px] w-full rounded-lg bg-white/5 border border-white/10 text-white px-3 py-2 opacity-80 whitespace-pre-wrap break-all">{{ $r['value'] }}</pre>
|
||||
@if($checked && filled(trim($r['actual'] ?? '')) && ($r['state'] ?? '') !== 'ok')
|
||||
<div class="text-[11px] text-white/60 break-words">
|
||||
<span class="opacity-70">Ist:</span>
|
||||
<span class="font-mono break-all">
|
||||
{{ str_replace('"', '', preg_replace('/\s+/', ' ', trim($r['actual']))) }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
{{-- Optional-Block unterhalb (einspaltig) --}}
|
||||
@if(!empty($optional))
|
||||
<div class="mt-5">
|
||||
<div class="mb-2 flex items-center gap-2 text-[11px]">
|
||||
<span class="px-2 py-0.5 rounded bg-slate-800/80 text-slate-200">Optional</span>
|
||||
<span class="text-slate-300/80">empfohlene Zusatz-Records</span>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
@foreach ($optional as $r)
|
||||
<div class="rounded-xl border {{ $r['boxClass'] ?? $stateColors['neutral'] }}">
|
||||
<div class="flex items-center justify-between px-4 py-2 text-[12px]">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="px-2 py-0.5 rounded {{ $recordColors[$r['type']] ?? 'bg-slate-700/50 text-slate-300' }}">
|
||||
{{ $r['type'] }}
|
||||
</span>
|
||||
<span class="text-slate-200">{{ $r['name'] }}</span>
|
||||
<span class="ml-2 text-[10px] px-2 py-0.5 rounded bg-white/5 border border-white/10 text-white/70">
|
||||
Optional
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2 text-slate-300/70">
|
||||
<x-button.copy-btn :text="$r['value']" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="px-4 pb-3 space-y-2">
|
||||
<pre class="text-[12px] w-full rounded-lg bg-white/5 border border-white/10 text-white px-3 py-2 opacity-80 whitespace-pre-wrap break-all">{{ $r['value'] }}</pre>
|
||||
|
||||
@if($checked && filled(trim($r['actual'] ?? '')) && ($r['state'] ?? '') !== 'ok')
|
||||
<div class="text-[11px] text-white/60 break-words">
|
||||
<span class="opacity-70">Ist:</span>
|
||||
<span class="font-mono break-all">
|
||||
{{ str_replace('"', '', preg_replace('/\s+/', ' ', trim($r['actual']))) }}
|
||||
</span>
|
||||
</div>
|
||||
@endif
|
||||
@if(!empty($r['helpUrl']))
|
||||
<div>
|
||||
<span class="text-xs text-white/60">{{ $r['info'] }}</span>
|
||||
</div>
|
||||
<a href="{{ $r['helpUrl'] }}" target="_blank" rel="noopener"
|
||||
class="mt-2 inline-flex items-center gap-1 text-[12px] text-sky-300 hover:text-sky-200">
|
||||
<i class="ph ph-arrow-square-out"></i>{{ $r['helpLabel'] }}
|
||||
</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@push('modal.footer')
|
||||
<div class="px-5 py-3 border-t border-white/10 backdrop-blur rounded-b-2xl">
|
||||
<div class="flex flex-wrap items-center gap-4 text-[12px] text-slate-300">
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<i class="ph ph-check-circle text-emerald-300"></i> vorhanden
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<i class="ph ph-warning text-amber-300"></i> abweichend
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<i class="ph ph-x-circle text-rose-300"></i> fehlt
|
||||
</span>
|
||||
|
||||
<span class="ml-auto">
|
||||
<div class="flex items-center gap-2">
|
||||
<button wire:click="$dispatch('domain:check-dns')" wire:loading.attr="disabled"
|
||||
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-[12px]
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<i class="ph ph-check-circle text-emerald-300"></i> vorhanden
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<i class="ph ph-warning text-amber-300"></i> Syntaxfehler
|
||||
</span>
|
||||
<span class="inline-flex items-center gap-1">
|
||||
<i class="ph ph-x-circle text-rose-300"></i> fehlt (nur Pflicht)
|
||||
</span>
|
||||
|
||||
<span class="ml-auto"></span>
|
||||
|
||||
<button wire:click="$dispatch('domain:check-dns')" wire:loading.attr="disabled"
|
||||
class="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-[12px]
|
||||
bg-white/5 border border-white/10 hover:bg-white/10">
|
||||
<i class="ph ph-magnifying-glass"></i>
|
||||
<span wire:loading.remove>DNS prüfen</span>
|
||||
<span wire:loading>prüfe…</span>
|
||||
</button>
|
||||
<button wire:click="$dispatch('closeModal')"
|
||||
class="px-3 py-1.5 rounded-lg text-sm bg-emerald-500/20 text-emerald-300 border border-emerald-400/30 hover:bg-emerald-500/30">
|
||||
<i class="ph ph-check"></i> Fertig
|
||||
</button>
|
||||
</div>
|
||||
</span>
|
||||
<i class="ph ph-magnifying-glass"></i>
|
||||
<span wire:loading.remove>DNS prüfen</span>
|
||||
<span wire:loading>prüfe…</span>
|
||||
</button>
|
||||
|
||||
<button wire:click="$dispatch('closeModal')"
|
||||
class="px-3 py-1.5 rounded-lg text-sm bg-emerald-500/20 text-emerald-300
|
||||
border border-emerald-400/30 hover:bg-emerald-500/30">
|
||||
<i class="ph ph-check"></i> Fertig
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@endpush
|
||||
|
||||
{{--@push('modal.header')--}}
|
||||
{{-- <div class="px-5 pt-5 pb-3 border-b border-white/10--}}
|
||||
{{-- backdrop-blur rounded-t-2xl relative">--}}
|
||||
{{-- <span class="absolute top-3 right-3 z-20 inline-flex items-center gap-2--}}
|
||||
{{-- rounded-full border border-slate-700/70 bg-slate-800/70--}}
|
||||
{{-- px-3 py-1 text-[11px] text-slate-200 shadow-sm">--}}
|
||||
{{-- <i class="ph ph-clock text-slate-300"></i> TTL: {{ $ttl }}--}}
|
||||
{{-- </span>--}}
|
||||
{{-- <h2 class="text-[18px] font-semibold text-slate-100">DNS-Einträge</h2>--}}
|
||||
|
||||
{{-- <p class="text-[13px] text-slate-300/80">--}}
|
||||
{{-- Setze die folgenden Records für--}}
|
||||
{{-- <span class="text-sky-300 underline decoration-sky-500/40 underline-offset-2">{{ $zone }}</span>.--}}
|
||||
{{-- </p>--}}
|
||||
{{-- </div>--}}
|
||||
{{--@endpush--}}
|
||||
|
||||
{{--<div class="relative p-5">--}}
|
||||
{{-- <div class="space-y-5">--}}
|
||||
{{-- --}}{{-- GRID: links (Mail-Records), rechts (Infrastruktur) --}}
|
||||
{{-- <div class="grid grid-cols-1 lg:grid-cols-2 gap-5">--}}
|
||||
{{-- --}}{{-- Mail-Records --}}
|
||||
{{-- <section>--}}
|
||||
{{-- <div class="mb-2 flex items-center gap-2 text-[11px]">--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded bg-slate-800/80 text-slate-200">Step 1</span>--}}
|
||||
{{-- <span class="text-slate-300/80">Mail-Records</span>--}}
|
||||
{{-- <span class="ml-auto px-2 py-0.5 rounded bg-indigo-600/20 text-indigo-200 border border-indigo-500/20">--}}
|
||||
{{-- <i class="ph ph-seal-check mr-1"></i>Absenderdomain--}}
|
||||
{{-- </span>--}}
|
||||
{{-- </div>--}}
|
||||
|
||||
{{-- <div class="space-y-4">--}}
|
||||
{{-- @foreach ($dynamic as $r)--}}
|
||||
{{-- <div class="rounded-xl border--}}
|
||||
{{-- {{ $checked ? ($stateColors[$r['state'] ?? 'neutral'] ?? $stateColors['neutral']) : $stateColors['neutral'] }}">--}}
|
||||
{{-- <div class="flex items-center justify-between px-4 py-2 text-[12px]">--}}
|
||||
{{-- <div class="flex items-center gap-2">--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded {{ $recordColors[$r['type']] ?? 'bg-slate-700/50 text-slate-300' }}">--}}
|
||||
{{-- {{ $r['type'] }}--}}
|
||||
{{-- </span>--}}
|
||||
{{-- <span class="text-slate-200">{{ $r['name'] }}</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="flex items-center gap-2 text-slate-300/70">--}}
|
||||
{{-- <x-button.copy-btn :text="$r['value']" />--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="px-4 pb-3">--}}
|
||||
{{-- <pre class="text-[12px] w-full rounded-lg bg-white/5 border border-white/10 text-white px-3 py-2 text-sm opacity-70 whitespace-pre-wrap break-all">{{ $r['value'] }}</pre>--}}
|
||||
{{-- @if(!empty($r['helpUrl']))--}}
|
||||
{{-- <a href="{{ $r['helpUrl'] }}" target="_blank" rel="noopener"--}}
|
||||
{{-- class="mt-2 inline-flex items-center gap-1 text-[12px] text-sky-300 hover:text-sky-200">--}}
|
||||
{{-- <i class="ph ph-arrow-square-out"></i>{{ $r['helpLabel'] }}--}}
|
||||
{{-- </a>--}}
|
||||
{{-- @endif--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="rounded-xl border border-white/10 bg-white/[0.04] #rounded-xl #border #border-slate-700/50 #bg-slate-900/60">--}}
|
||||
{{-- <div class="flex items-center justify-between px-4 py-2 text-[12px]">--}}
|
||||
{{-- <div class="flex items-center gap-2">--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded {{ $recordColors[$r['type']] ?? 'bg-slate-700/50 text-slate-300' }}">{{ $r['type'] }}</span>--}}
|
||||
{{-- <span class="text-slate-200">{{ $r['name'] }}</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="flex items-center gap-2 text-slate-300/70">--}}
|
||||
{{-- <x-button.copy-btn :text="$r['value']" />--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="px-4 pb-3">--}}
|
||||
{{-- <pre class="text-[12px] w-full rounded-lg bg-white/5 border border-white/10 text-white px-3 py-2 text-sm opacity-70 whitespace-pre-wrap break-all">{{ $r['value'] }}</pre>--}}
|
||||
{{-- @if(!empty($r['helpUrl']))--}}
|
||||
{{-- <a href="{{ $r['helpUrl'] }}" target="_blank" rel="noopener"--}}
|
||||
{{-- class="mt-2 inline-flex items-center gap-1 text-[12px] text-sky-300 hover:text-sky-200">--}}
|
||||
{{-- <i class="ph ph-arrow-square-out"></i>{{ $r['helpLabel'] }}--}}
|
||||
{{-- </a>--}}
|
||||
{{-- @endif--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @endforeach--}}
|
||||
|
||||
{{-- @foreach ($optional as $r)--}}
|
||||
{{-- <div class="rounded-xl border border-white/10 bg-white/[0.04] #rounded-xl #border #border-slate-700/50 #bg-slate-900/60">--}}
|
||||
{{-- <div class="flex items-center justify-between px-4 py-2 text-[12px]">--}}
|
||||
{{-- <div class="flex items-center gap-2">--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded {{ $recordColors[$r['type']] ?? 'bg-slate-700/50 text-slate-300' }}">{{ $r['type'] }}</span>--}}
|
||||
{{-- <span class="text-slate-200">{{ $r['name'] }}</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="flex items-center gap-2 text-slate-300/70">--}}
|
||||
{{-- <span class="text-[11px] px-2 py-0.5 rounded {{ $recordColors['OPTIONAL'] ?? 'bg-slate-700/50 text-slate-300' }}">Optional</span>--}}
|
||||
{{-- <x-button.copy-btn :text="$r['value']" />--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="px-4 pb-3">--}}
|
||||
{{-- <pre class="text-[12px] w-full rounded-lg bg-white/5 border border-white/10 text-white px-3 py-2 text-sm opacity-70 whitespace-pre-wrap break-all">{{ $r['value'] }}</pre>--}}
|
||||
{{-- @if(!empty($r['helpUrl']))--}}
|
||||
{{-- <a href="{{ $r['helpUrl'] }}" target="_blank" rel="noopener"--}}
|
||||
{{-- class="mt-2 inline-flex items-center gap-1 text-[12px] text-sky-300 hover:text-sky-200">--}}
|
||||
{{-- <i class="ph ph-arrow-square-out"></i>{{ $r['helpLabel'] }}--}}
|
||||
{{-- </a>--}}
|
||||
{{-- @endif--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @endforeach--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </section>--}}
|
||||
|
||||
{{-- --}}{{-- Globale Infrastruktur --}}
|
||||
{{-- <section>--}}
|
||||
{{-- <div class="mb-2 flex items-center gap-2 text-[11px]">--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded bg-slate-800/80 text-slate-200">Step 2</span>--}}
|
||||
{{-- <span class="text-slate-300/80">Globale Infrastruktur (MTA-Host)</span>--}}
|
||||
{{-- <span class="text-slate-400/70">gilt für alle Domains</span>--}}
|
||||
{{-- </div>--}}
|
||||
|
||||
{{-- <div class="space-y-4">--}}
|
||||
{{-- @foreach ($static as $r)--}}
|
||||
{{-- <div class="rounded-xl border--}}
|
||||
{{-- {{ $checked ? ($stateColors[$r['state'] ?? 'neutral'] ?? $stateColors['neutral']) : $stateColors['neutral'] }}">--}}
|
||||
{{-- <div class="flex items-center justify-between px-4 py-2 text-[12px]">--}}
|
||||
{{-- <div class="flex items-center gap-2">--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded {{ $recordColors[$r['type']] ?? 'bg-slate-700/50 text-slate-300' }}">--}}
|
||||
{{-- {{ $r['type'] }}--}}
|
||||
{{-- </span>--}}
|
||||
{{-- <span class="text-slate-200">{{ $r['name'] }}</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="flex items-center gap-2 text-slate-300/70">--}}
|
||||
{{-- <x-button.copy-btn :text="$r['value']" />--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="px-4 pb-3">--}}
|
||||
{{-- <pre class="text-[12px] w-full rounded-lg bg-white/5 border border-white/10 text-white px-3 py-2 text-sm opacity-70 whitespace-pre-wrap break-all">{{ $r['value'] }}</pre>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
|
||||
{{-- <div class="rounded-xl border border-white/10 bg-white/[0.04] #rounded-xl #border #border-slate-700/50 #bg-slate-900/60">--}}
|
||||
{{-- <div class="flex items-center justify-between px-4 py-2 text-[12px]">--}}
|
||||
{{-- <div class="flex items-center gap-2">--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded {{ $recordColors[$r['type']] ?? 'bg-slate-700/50 text-slate-300' }}">{{ $r['type'] }}</span>--}}
|
||||
{{-- <span class="text-slate-200">{{ $r['name'] }}</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="flex items-center gap-2 text-slate-300/70">--}}
|
||||
{{-- <x-button.copy-btn :text="$r['value']" />--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="px-4 pb-3">--}}
|
||||
{{-- <pre class="text-[12px] w-full rounded-lg bg-white/5 border border-white/10 text-white px-3 py-2 text-sm opacity-70 whitespace-pre-wrap break-all">{{ $r['value'] }}</pre>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @endforeach--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </section>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{--</div>--}}
|
||||
|
||||
{{--@push('modal.footer')--}}
|
||||
{{-- <div class="px-5 py-3 border-t border-white/10 backdrop-blur rounded-b-2xl">--}}
|
||||
{{-- <div class="flex flex-wrap items-center gap-4 text-[12px] text-slate-300">--}}
|
||||
{{-- <span class="inline-flex items-center gap-1">--}}
|
||||
{{-- <i class="ph ph-check-circle text-emerald-300"></i> vorhanden--}}
|
||||
{{-- </span>--}}
|
||||
{{-- <span class="inline-flex items-center gap-1">--}}
|
||||
{{-- <i class="ph ph-warning text-amber-300"></i> abweichend--}}
|
||||
{{-- </span>--}}
|
||||
{{-- <span class="inline-flex items-center gap-1">--}}
|
||||
{{-- <i class="ph ph-x-circle text-rose-300"></i> fehlt--}}
|
||||
{{-- </span>--}}
|
||||
|
||||
{{-- <span class="ml-auto">--}}
|
||||
{{-- <div class="flex items-center gap-2">--}}
|
||||
{{-- <button wire:click="$dispatch('domain:check-dns')" wire:loading.attr="disabled"--}}
|
||||
{{-- class="inline-flex items-center gap-2 px-3 py-1.5 rounded-lg text-[12px]--}}
|
||||
{{-- bg-white/5 border border-white/10 hover:bg-white/10">--}}
|
||||
{{-- <i class="ph ph-magnifying-glass"></i>--}}
|
||||
{{-- <span wire:loading.remove>DNS prüfen</span>--}}
|
||||
{{-- <span wire:loading>prüfe…</span>--}}
|
||||
{{-- </button>--}}
|
||||
{{-- <button wire:click="$dispatch('closeModal')"--}}
|
||||
{{-- class="px-3 py-1.5 rounded-lg text-sm bg-emerald-500/20 text-emerald-300 border border-emerald-400/30 hover:bg-emerald-500/30">--}}
|
||||
{{-- <i class="ph ph-check"></i> Fertig--}}
|
||||
{{-- </button>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{--@endpush--}}
|
||||
{{--@push('modal.footer')--}}
|
||||
{{-- <div class="px-5 py-3 border-t border-white/10 backdrop-blur rounded-b-2xl">--}}
|
||||
{{-- <div class="flex justify-end">--}}
|
||||
|
|
|
|||
|
|
@ -1,22 +1,192 @@
|
|||
<div wire:poll.60s="refresh" class="glass-card p-4 rounded-2xl border border-white/10 bg-white/5">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<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">DKIM / DMARC / TLSA</span>
|
||||
<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)
|
||||
<div class="py-2 flex items-center justify-between">
|
||||
<div class="text-white/85">{{ $r['dom'] }}</div>
|
||||
<div class="flex items-center gap-2 text-xs">
|
||||
<span class="px-2 py-0.5 rounded-full border {{ $r['dkim'] ? 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10' : 'text-rose-300 border-rose-400/30 bg-rose-500/10' }}">DKIM</span>
|
||||
<span class="px-2 py-0.5 rounded-full border {{ $r['dmarc'] ? 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10' : 'text-rose-300 border-rose-400/30 bg-rose-500/10' }}">DMARC</span>
|
||||
<span class="px-2 py-0.5 rounded-full border {{ $r['tlsa'] ? 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10' : 'text-rose-300 border-rose-400/30 bg-rose-500/10' }}">TLSA</span>
|
||||
</div>
|
||||
</div>
|
||||
<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">--}}
|
||||
{{-- <i class="ph ph-globe-stand text-white/70 text-[13px]"></i>--}}
|
||||
{{-- <span class="text-[11px] uppercase text-white/70 tracking-wide">DNS / Mail Health</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="text-xs text-white/60">--}}
|
||||
{{-- <span class="opacity-70">IP:</span>--}}
|
||||
{{-- <span class="text-white/90 font-mono">{{ $ipv4 ?? '–' }}</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
|
||||
{{-- --}}{{-- Hostweite TLSA-Anzeige --}}
|
||||
{{-- <div class="mb-4 flex items-center gap-2 text-xs">--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded-full border--}}
|
||||
{{-- {{ $tlsa ? 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10'--}}
|
||||
{{-- : 'text-rose-300 border-rose-400/30 bg-rose-500/10' }}">--}}
|
||||
{{-- TLSA ({{ $host }})--}}
|
||||
{{-- </span>--}}
|
||||
{{-- <span class="text-white/45">für 25/465/587</span>--}}
|
||||
{{-- </div>--}}
|
||||
|
||||
{{-- <div class="divide-y divide-white/5">--}}
|
||||
{{-- @forelse($domains as $dom)--}}
|
||||
{{-- <div class="py-2 flex items-center justify-between">--}}
|
||||
{{-- <div class="text-white/85">{{ $dom['name'] }}</div>--}}
|
||||
{{-- <div class="flex items-center gap-2 text-xs">--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded-full border--}}
|
||||
{{-- {{ $dom['dkim'] ? 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10'--}}
|
||||
{{-- : 'text-rose-300 border-rose-400/30 bg-rose-500/10' }}">--}}
|
||||
{{-- DKIM--}}
|
||||
{{-- </span>--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded-full border--}}
|
||||
{{-- {{ $dom['dmarc'] ? 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10'--}}
|
||||
{{-- : 'text-rose-300 border-rose-400/30 bg-rose-500/10' }}">--}}
|
||||
{{-- DMARC--}}
|
||||
{{-- </span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @empty--}}
|
||||
{{-- <div class="py-4 text-sm text-white/60">Keine Domains.</div>--}}
|
||||
{{-- @endforelse--}}
|
||||
{{-- </div>--}}
|
||||
{{--</div>--}}
|
||||
|
||||
{{--<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-database text-white/70 text-[13px]"></i>--}}
|
||||
{{-- <span class="text-[11px] uppercase text-white/70 tracking-wide">DNS Overview</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="text-xs text-white/60">--}}
|
||||
{{-- IP: <span class="text-white/90 font-mono">{{ $ipv4 ?? '–' }}</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
|
||||
{{-- @foreach($domains as $dom)--}}
|
||||
{{-- <div class="border-t border-white/5 py-3">--}}
|
||||
{{-- <div class="text-white/85 font-medium">{{ $dom['name'] }}</div>--}}
|
||||
{{-- <div class="grid grid-cols-2 md:grid-cols-3 gap-2 mt-2 text-xs">--}}
|
||||
{{-- @foreach($dom['checks'] as $label => $status)--}}
|
||||
{{-- <div class="flex items-center gap-1">--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded-full border--}}
|
||||
{{-- {{ $status ? 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10'--}}
|
||||
{{-- : 'text-rose-300 border-rose-400/30 bg-rose-500/10' }}">--}}
|
||||
{{-- {{ strtoupper($label) }}--}}
|
||||
{{-- </span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @endforeach--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @endforeach--}}
|
||||
{{--</div>--}}
|
||||
|
||||
{{--<div wire:poll.60s="refresh" class="glass-card p-4 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">DKIM / DMARC / TLSA</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="text-xs text-white/60">--}}
|
||||
{{-- <span class="opacity-70">IP:</span>--}}
|
||||
{{-- <span class="text-white/90 font-mono">--}}
|
||||
{{-- {{ $ipv4 ?? '–' }}{{ $ipv6 ? ' / '.$ipv6 : '' }}--}}
|
||||
{{-- </span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
|
||||
{{-- --}}{{-- Host-Status (TLSA) --}}
|
||||
{{-- <div class="mb-4 flex items-center justify-between px-3 py-2 rounded-xl border border-white/10 bg-white/5">--}}
|
||||
{{-- <div class="flex items-center gap-2">--}}
|
||||
{{-- <i class="ph ph-server text-white/60 text-[15px]"></i>--}}
|
||||
{{-- <span class="text-white/85 font-mono">{{ $host }}</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div>--}}
|
||||
{{-- <span class="px-2 py-0.5 text-xs rounded-full border--}}
|
||||
{{-- {{ $tlsa ? 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10'--}}
|
||||
{{-- : 'text-rose-300 border-rose-400/30 bg-rose-500/10' }}">--}}
|
||||
{{-- TLSA--}}
|
||||
{{-- </span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
|
||||
{{-- --}}{{-- Domainliste: DKIM/DMARC --}}
|
||||
{{-- <div class="divide-y divide-white/5">--}}
|
||||
{{-- @forelse($rows as $r)--}}
|
||||
{{-- <div class="py-2 flex items-center justify-between">--}}
|
||||
{{-- <div class="text-white/85">{{ $r['dom'] }}</div>--}}
|
||||
{{-- <div class="flex items-center gap-2 text-xs">--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded-full border--}}
|
||||
{{-- {{ $r['dkim'] ? 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10'--}}
|
||||
{{-- : 'text-rose-300 border-rose-400/30 bg-rose-500/10' }}">--}}
|
||||
{{-- DKIM--}}
|
||||
{{-- </span>--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded-full border--}}
|
||||
{{-- {{ $r['dmarc'] ? 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10'--}}
|
||||
{{-- : 'text-rose-300 border-rose-400/30 bg-rose-500/10' }}">--}}
|
||||
{{-- DMARC--}}
|
||||
{{-- </span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @empty--}}
|
||||
{{-- <div class="py-4 text-sm text-white/60 text-center">Keine aktiven Domains gefunden.</div>--}}
|
||||
{{-- @endforelse--}}
|
||||
{{-- </div>--}}
|
||||
{{--</div>--}}
|
||||
|
||||
{{--<div wire:poll.60s="refresh" class="glass-card p-4 rounded-2xl border border-white/10 bg-white/5">--}}
|
||||
{{-- <div class="flex items-center justify-between mb-3">--}}
|
||||
{{-- <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">DKIM / DMARC / TLSA</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="divide-y divide-white/5">--}}
|
||||
{{-- @forelse($rows as $r)--}}
|
||||
{{-- <div class="py-2 flex items-center justify-between">--}}
|
||||
{{-- <div class="text-white/85">{{ $r['dom'] }}</div>--}}
|
||||
{{-- <div class="flex items-center gap-2 text-xs">--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded-full border {{ $r['dkim'] ? 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10' : 'text-rose-300 border-rose-400/30 bg-rose-500/10' }}">DKIM</span>--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded-full border {{ $r['dmarc'] ? 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10' : 'text-rose-300 border-rose-400/30 bg-rose-500/10' }}">DMARC</span>--}}
|
||||
{{-- <span class="px-2 py-0.5 rounded-full border {{ $r['tlsa'] ? 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10' : 'text-rose-300 border-rose-400/30 bg-rose-500/10' }}">TLSA</span>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @empty--}}
|
||||
{{-- <div class="py-4 text-sm text-white/60">Keine Domains.</div>--}}
|
||||
{{-- @endforelse--}}
|
||||
{{-- </div>--}}
|
||||
{{--</div>--}}
|
||||
|
|
|
|||
|
|
@ -1,50 +1,68 @@
|
|||
<div class="glass-card p-4 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">
|
||||
<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-shield-warning text-white/70 text-[13px]"></i>
|
||||
<span class="text-[11px] uppercase text-white/70">Reputation / RBL</span>
|
||||
<i class="ph ph-shield text-white/70 text-[13px]"></i>
|
||||
<span class="text-[11px] uppercase text-white/70 tracking-wide">Reputation / RBL</span>
|
||||
</div>
|
||||
|
||||
<div class="text-xs text-white/70">
|
||||
IP: <span class="text-white/90">{{ $ip }}</span>
|
||||
<div class="text-xs text-white/60">
|
||||
<span class="opacity-70">IP:</span>
|
||||
<span class="text-white/90 font-mono">{{ $ip }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="mt-5">
|
||||
@if($hits === 0)
|
||||
<div class="flex items-center gap-2 text-emerald-300">
|
||||
<i class="ph ph-shield-check-thin text-lg"></i>
|
||||
<span>Deine IP ist aktuell auf keiner der geprüften Blacklists.</span>
|
||||
<div class="flex items-center gap-3 px-4 py-3 rounded-xl bg-emerald-500/10 border border-emerald-400/20">
|
||||
<div class="flex items-center justify-center w-8 h-8 rounded-full bg-emerald-500/20">
|
||||
<i class="ph ph-shield-check text-emerald-300 text-lg"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-emerald-300 font-medium text-[15px]">
|
||||
Deine IP ist auf keiner bekannten Blacklist.
|
||||
</div>
|
||||
<div class="text-[12px] text-white/50 mt-0.5">
|
||||
Gute Reputation – keine Auffälligkeiten gefunden.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-2 text-[11px] text-white/45">
|
||||
Geprüfte öffentliche RBLs: Spamhaus, PSBL, UCEPROTECT-1, s5h.
|
||||
<div class="mt-3 text-[11px] text-white/45 text-center">
|
||||
Geprüfte öffentliche RBLs:
|
||||
<span class="text-white/60">Spamhaus, PSBL, UCEPROTECT-1, s5h</span>
|
||||
</div>
|
||||
@else
|
||||
<div class="flex items-center gap-2 text-rose-300">
|
||||
<i class="ph ph-warning-circle text-lg"></i>
|
||||
<span>{{ $hits }} Treffer auf Blacklists:</span>
|
||||
<div class="flex items-center gap-3 px-4 py-3 rounded-xl bg-rose-500/10 border border-rose-400/20">
|
||||
<div class="flex items-center justify-center w-8 h-8 rounded-full bg-rose-500/20">
|
||||
<i class="ph ph-warning-circle text-rose-300 text-lg"></i>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-rose-300 font-medium text-[15px]">
|
||||
{{ $hits }} {{ Str::plural('Treffer', $hits) }} auf Blacklists
|
||||
</div>
|
||||
<ul class="mt-1 space-y-1 text-sm text-rose-300/90">
|
||||
@foreach($lists as $l)
|
||||
<li class="flex items-center gap-2">
|
||||
<span class="inline-block w-1.5 h-1.5 rounded-full bg-rose-400/80"></span>
|
||||
<span>{{ $l }}</span>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<ul class="mt-2 space-y-1 text-sm text-rose-300">
|
||||
@foreach($lists as $l)
|
||||
<li class="flex items-center gap-2">
|
||||
<span class="inline-block w-1.5 h-1.5 rounded-full bg-rose-400"></span>
|
||||
<span>{{ $l }}</span>
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="mt-3">
|
||||
<div class="mt-5 flex justify-center">
|
||||
<button wire:click="refresh"
|
||||
class="inline-flex items-center gap-1.5 rounded-full text-[11px] px-3 py-1
|
||||
class="inline-flex items-center gap-1.5 rounded-full text-[12px] px-4 py-1.5
|
||||
text-white/80 bg-white/10 border border-white/15
|
||||
hover:bg-white/15 hover:text-white">
|
||||
<i class="ph ph-arrows-clockwise text-[12px]"></i>
|
||||
hover:bg-white/15 hover:text-white transition">
|
||||
<i class="ph ph-arrows-clockwise text-[13px]"></i>
|
||||
Neu prüfen
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{--<div class="glass-card p-4 rounded-2xl border border-white/10 bg-white/5">--}}
|
||||
{{-- <div class="flex items-center justify-between">--}}
|
||||
{{-- <div class="inline-flex items-center gap-2 bg-white/5 border border-white/10 px-2.5 py-1 rounded-full">--}}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,8 @@ Schedule::job(RunHealthChecks::class)->everyMinute()->withoutOverlapping();
|
|||
Schedule::command('spamav:collect')->everyFiveMinutes()->withoutOverlapping();
|
||||
//Schedule::command('mailwolt:check-updates')->dailyAt('04:10');
|
||||
Schedule::command('mailwolt:check-updates')->everytwoMinutes();
|
||||
Schedule::command('rbl:probe')->weeklyOn(0, '3:30')->withoutOverlapping();
|
||||
|
||||
|
||||
Schedule::command('mail:update-stats')->everyFiveMinutes()->withoutOverlapping();
|
||||
Schedule::command('health:probe-disk', ['target' => '/', '--ttl' => 900])->everyTenMinutes();
|
||||
|
|
|
|||
Loading…
Reference in New Issue