Fix: Mailbox Stats über Dovecot mit config/mailpool.php

main v1.0.91
boban 2025-10-29 02:45:40 +01:00
parent c8cae445c5
commit 659f3cb7ae
9 changed files with 2255 additions and 450 deletions

View File

@ -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

View File

@ -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) !== '';
// }
//}

View File

@ -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;
//

View File

@ -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';

View File

@ -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">--}}

View File

@ -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>--}}

View File

@ -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">--}}

View File

@ -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();