From 300928851a70762ec0a1b4830babbf6c15befa8d Mon Sep 17 00:00:00 2001 From: boban Date: Tue, 21 Oct 2025 20:54:00 +0200 Subject: [PATCH] =?UTF-8?q?Fix:=20Mailbox=20Stats=20=C3=BCber=20Dovecot=20?= =?UTF-8?q?mit=20config/mailpool.php?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/Console/Commands/UpdateMailboxStats.php | 316 +++++++--- app/Livewire/Ui/Mail/MailboxList.php | 608 ++++++++++++-------- 2 files changed, 607 insertions(+), 317 deletions(-) diff --git a/app/Console/Commands/UpdateMailboxStats.php b/app/Console/Commands/UpdateMailboxStats.php index 8fab4ce..048b261 100644 --- a/app/Console/Commands/UpdateMailboxStats.php +++ b/app/Console/Commands/UpdateMailboxStats.php @@ -13,128 +13,98 @@ use RecursiveIteratorIterator; class UpdateMailboxStats extends Command { protected $signature = 'mail:update-stats {--user=}'; - protected $description = 'Aktualisiert Quota & Nachrichtenzahl (Settings/Redis; ohne MailUser-DB-Felder).'; + protected $description = 'Aktualisiert Quota & Nachrichtenzahl (Settings/Redis; ohne DB-Spalten).'; public function handle(): int { $log = Log::channel('mailstats'); - $onlyUser = trim((string)$this->option('user')) ?: null; - $started = microtime(true); + $t0 = microtime(true); - $q = MailUser::query() - ->with('domain:id,domain,is_system') + // Basis-Query: nur aktive, keine System-Mailboxen und keine System-Domains + $base = MailUser::query() + ->select(['id', 'domain_id', 'localpart', 'email', 'is_active', 'is_system']) + ->with(['domain:id,domain,is_system']) ->where('is_active', true) - ->where('is_system', false) // keine System-Mailboxen - ->whereHas('domain', fn($d) => $d->where('is_system', false)); // keine Systemdomains + ->where('is_system', false) + ->whereHas('domain', fn($d) => $d->where('is_system', false)); if ($onlyUser) { - $q->where('email', $onlyUser); + $base->where('email', $onlyUser); } - $users = $q->get(); - - $log->info('mail:update-stats START', [ - 'only' => $onlyUser, - 'users' => $users->count(), - ]); - - if ($users->isEmpty()) { - $this->warn('Keine passenden Mailboxen gefunden.'); - $log->info('mail:update-stats DONE (no users)', ['ms' => (int)((microtime(true) - $started) * 1000)]); - return self::SUCCESS; - } - - $changed = 0; $checked = 0; + $changed = 0; - foreach ($users as $u) { - $checked++; + $log->info('mail:update-stats START', ['only' => $onlyUser]); - // Email robust ermitteln - $raw = (string)($u->getRawOriginal('email') ?? ''); - $email = $raw !== '' ? $raw : ($u->email ?? $u->address ?? null); + $base->orderBy('id')->chunkById(200, function ($users) use (&$checked, &$changed, $log) { + foreach ($users as $u) { + $checked++; - if (!is_string($email) || !preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) { - // nur debug – vermeidet Spam - $log->debug('skip invalid email', ['user_id' => $u->id, 'raw' => $raw, 'computed' => $email]); - continue; - } + // Email robust bestimmen (raw -> accessor -> zusammengesetzt) + $raw = (string)($u->getRawOriginal('email') ?? ''); + $email = $raw !== '' ? $raw : ($u->email ?? $u->address ?? null); - [$local, $domain] = explode('@', $email, 2); - $maildir = "/var/mail/vhosts/{$domain}/{$local}"; + if (!is_string($email) || !preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) { + // still – kein Log-Spam + continue; + } - // Größe (Dateisystem) - $usedBytes = 0; - if (is_dir($maildir)) { - $it = new RecursiveIteratorIterator( - new RecursiveDirectoryIterator($maildir, \FilesystemIterator::SKIP_DOTS) - ); - foreach ($it as $f) { - if ($f->isFile()) { - $usedBytes += $f->getSize(); + [$local, $domain] = explode('@', $email, 2); + $maildir = "/var/mail/vhosts/{$domain}/{$local}"; + + // Größe in Bytes (rekursiv) + $usedBytes = 0; + if (is_dir($maildir)) { + $it = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($maildir, \FilesystemIterator::SKIP_DOTS) + ); + foreach ($it as $f) { + if ($f->isFile()) $usedBytes += $f->getSize(); } } + + // Message-Count + $messageCount = $this->countViaDoveadm($email); + if ($messageCount === null) { + $messageCount = $this->countViaFilesystem($maildir); + } + + $key = "mailbox.{$email}"; + $prev = (array)(Setting::get($key, []) ?: []); + $new = [ + 'used_bytes' => (int)$usedBytes, + 'message_count' => (int)$messageCount, + 'updated_at' => now()->toDateTimeString(), + ]; + + if (($prev['used_bytes'] ?? null) !== $new['used_bytes'] + || ($prev['message_count'] ?? null) !== $new['message_count']) { + Setting::set($key, $new); + $changed++; + + // kurze Ausgabe & Info-Log NUR bei Änderung + $this->line(sprintf("%-35s %7.1f MiB %5d msgs", + $email, $usedBytes / 1048576, $messageCount)); + $log->info('updated', ['email' => $email, 'used_bytes' => $new['used_bytes'], 'message_count' => $new['message_count']]); + } } + }); - // Nachrichten zählen - $messageCount = $this->countViaDoveadm($email); - if ($messageCount === null) { - $messageCount = $this->countViaFilesystem($maildir); - } - - // Nur bei Änderungen persistieren + loggen - $key = "mailbox.{$email}"; - $prev = Setting::get($key, null) ?: []; - - $new = [ - 'used_bytes' => (int)$usedBytes, - 'message_count' => (int)$messageCount, - 'updated_at' => now()->toDateTimeString(), - ]; - - if ( - !is_array($prev) || - ($prev['used_bytes'] ?? null) !== $new['used_bytes'] || - ($prev['message_count'] ?? null) !== $new['message_count'] - ) { - Setting::set($key, $new); - $changed++; - - // kurze, nützliche Info – nur bei Änderung - $this->line(sprintf("%-35s %7.1f MiB %5d msgs", - $email, $usedBytes / 1024 / 1024, $messageCount)); - - $log->info('updated', [ - 'email' => $email, - 'used_bytes' => $new['used_bytes'], - 'message_count' => $new['message_count'], - ]); - } else { - // keine Änderung → kein Info-Log - $log->debug('unchanged', ['email' => $email]); - } - } - - $ms = (int)((microtime(true) - $started) * 1000); - $log->info('mail:update-stats DONE', [ - 'checked' => $checked, - 'changed' => $changed, - 'ms' => $ms, - ]); - + $ms = (int)((microtime(true) - $t0) * 1000); + $log->info('mail:update-stats DONE', compact('checked', 'changed', 'ms')); $this->info('Mailbox-Statistiken aktualisiert.'); return self::SUCCESS; } - protected function countViaDoveadm(string $email): ?int + private function countViaDoveadm(string $email): ?int { $cmd = "sudo -n -u vmail /usr/bin/doveadm -f tab mailbox status -u " . escapeshellarg($email) . " messages INBOX 2>&1"; $out = []; $rc = 0; exec($cmd, $out, $rc); - if ($rc !== 0) return null; foreach ($out as $line) { @@ -145,7 +115,7 @@ class UpdateMailboxStats extends Command return null; } - protected function countViaFilesystem(string $maildir): int + private function countViaFilesystem(string $maildir): int { $n = 0; foreach (['cur', 'new'] as $sub) { @@ -163,6 +133,168 @@ class UpdateMailboxStats extends Command } } +//namespace App\Console\Commands; +// +//use App\Models\MailUser; +//use App\Models\Setting; +//use Illuminate\Console\Command; +//use Illuminate\Support\Facades\Log; +//use RecursiveDirectoryIterator; +//use RecursiveIteratorIterator; +// +//class UpdateMailboxStats extends Command +//{ +// protected $signature = 'mail:update-stats {--user=}'; +// protected $description = 'Aktualisiert Quota & Nachrichtenzahl (Settings/Redis; ohne MailUser-DB-Felder).'; +// +// public function handle(): int +// { +// $log = Log::channel('mailstats'); +// +// $onlyUser = trim((string)$this->option('user')) ?: null; +// $started = microtime(true); +// +// $q = MailUser::query() +// ->with('domain:id,domain,is_system') +// ->where('is_active', true) +// ->where('is_system', false) // keine System-Mailboxen +// ->whereHas('domain', fn($d) => $d->where('is_system', false)); // keine Systemdomains +// +// if ($onlyUser) { +// $q->where('email', $onlyUser); +// } +// +// $users = $q->get(); +// +// $log->info('mail:update-stats START', [ +// 'only' => $onlyUser, +// 'users' => $users->count(), +// ]); +// +// if ($users->isEmpty()) { +// $this->warn('Keine passenden Mailboxen gefunden.'); +// $log->info('mail:update-stats DONE (no users)', ['ms' => (int)((microtime(true) - $started) * 1000)]); +// return self::SUCCESS; +// } +// +// $changed = 0; +// $checked = 0; +// +// foreach ($users as $u) { +// $checked++; +// +// // Email robust ermitteln +// $raw = (string)($u->getRawOriginal('email') ?? ''); +// $email = $raw !== '' ? $raw : ($u->email ?? $u->address ?? null); +// +// if (!is_string($email) || !preg_match('/^[^@\s]+@[^@\s]+\.[^@\s]+$/', $email)) { +// // nur debug – vermeidet Spam +// $log->debug('skip invalid email', ['user_id' => $u->id, 'raw' => $raw, 'computed' => $email]); +// continue; +// } +// +// [$local, $domain] = explode('@', $email, 2); +// $maildir = "/var/mail/vhosts/{$domain}/{$local}"; +// +// // Größe (Dateisystem) +// $usedBytes = 0; +// if (is_dir($maildir)) { +// $it = new RecursiveIteratorIterator( +// new RecursiveDirectoryIterator($maildir, \FilesystemIterator::SKIP_DOTS) +// ); +// foreach ($it as $f) { +// if ($f->isFile()) { +// $usedBytes += $f->getSize(); +// } +// } +// } +// +// // Nachrichten zählen +// $messageCount = $this->countViaDoveadm($email); +// if ($messageCount === null) { +// $messageCount = $this->countViaFilesystem($maildir); +// } +// +// // Nur bei Änderungen persistieren + loggen +// $key = "mailbox.{$email}"; +// $prev = Setting::get($key, null) ?: []; +// +// $new = [ +// 'used_bytes' => (int)$usedBytes, +// 'message_count' => (int)$messageCount, +// 'updated_at' => now()->toDateTimeString(), +// ]; +// +// if ( +// !is_array($prev) || +// ($prev['used_bytes'] ?? null) !== $new['used_bytes'] || +// ($prev['message_count'] ?? null) !== $new['message_count'] +// ) { +// Setting::set($key, $new); +// $changed++; +// +// // kurze, nützliche Info – nur bei Änderung +// $this->line(sprintf("%-35s %7.1f MiB %5d msgs", +// $email, $usedBytes / 1024 / 1024, $messageCount)); +// +// $log->info('updated', [ +// 'email' => $email, +// 'used_bytes' => $new['used_bytes'], +// 'message_count' => $new['message_count'], +// ]); +// } else { +// // keine Änderung → kein Info-Log +// $log->debug('unchanged', ['email' => $email]); +// } +// } +// +// $ms = (int)((microtime(true) - $started) * 1000); +// $log->info('mail:update-stats DONE', [ +// 'checked' => $checked, +// 'changed' => $changed, +// 'ms' => $ms, +// ]); +// +// $this->info('Mailbox-Statistiken aktualisiert.'); +// return self::SUCCESS; +// } +// +// protected function countViaDoveadm(string $email): ?int +// { +// $cmd = "sudo -n -u vmail /usr/bin/doveadm -f tab mailbox status -u " +// . escapeshellarg($email) . " messages INBOX 2>&1"; +// $out = []; +// $rc = 0; +// exec($cmd, $out, $rc); +// +// if ($rc !== 0) return null; +// +// foreach ($out as $line) { +// if (preg_match('/^\s*INBOX\s+(\d+)\s*$/i', trim($line), $m)) { +// return (int)$m[1]; +// } +// } +// return null; +// } +// +// protected function countViaFilesystem(string $maildir): int +// { +// $n = 0; +// foreach (['cur', 'new'] as $sub) { +// $dir = "{$maildir}/{$sub}"; +// if (!is_dir($dir)) continue; +// $h = opendir($dir); +// if (!$h) continue; +// while (($fn = readdir($h)) !== false) { +// if ($fn === '.' || $fn === '..' || $fn[0] === '.') continue; +// $n++; +// } +// closedir($h); +// } +// return $n; +// } +//} + //namespace App\Console\Commands; // //use App\Models\MailUser; diff --git a/app/Livewire/Ui/Mail/MailboxList.php b/app/Livewire/Ui/Mail/MailboxList.php index 19dcc63..54b13a4 100644 --- a/app/Livewire/Ui/Mail/MailboxList.php +++ b/app/Livewire/Ui/Mail/MailboxList.php @@ -6,7 +6,6 @@ namespace App\Livewire\Ui\Mail; use App\Models\Domain; use App\Models\Setting; use Illuminate\Support\Facades\Artisan; -use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; use Livewire\Attributes\On; use Livewire\Component; @@ -27,15 +26,13 @@ class MailboxList extends Component #[On('focus:domain')] public function focusDomain(int $id): void { - // z. B. Domain nach oben holen / scrollen / highlighten - // oder direkt den "+ Postfach" Dialog: - // $this->openMailboxCreate($id); + // optional: Domain hervorheben } #[On('focus:user')] public function focusUser(int $id): void { - // später: Benutzerseite / Filter setzen ... + // optional } public function openMailboxCreate(int $domainId): void @@ -49,126 +46,89 @@ class MailboxList extends Component { // $domainId == mailbox_id $this->dispatch('openModal', component: 'ui.mail.modal.mailbox-edit-modal', arguments: [ - $domainId, // <— nur der Wert, kein Key! + $domainId, ]); } public function openMailboxDelete(int $domainId): void { $this->dispatch('openModal', component: 'ui.mail.modal.mailbox-delete-modal', arguments: [ - $domainId, // <— nur der Wert, kein Key! + $domainId, ]); } - public function updateMailboxStats() + public function updateMailboxStats(): void { - $started = microtime(true); - - Log::channel('mailstats')->info('UI: updateMailboxStats() geklickt', [ - 'actor' => 'web', - 'ip' => request()->ip() ?? null, - ]); - - // Command ausführen - $rc = Artisan::call('mail:update-stats'); - $output = Artisan::output(); - - Log::channel('mailstats')->info('UI: Command beendet', [ - 'rc' => $rc, - 'ms' => (int)((microtime(true) - $started) * 1000), - 'output' => trim($output), - ]); - - // UI auffrischen - $this->dispatch('$refresh'); - - // Ergebnis toaster - $this->dispatch('toast', - type: $rc === 0 ? 'done' : 'warn', - badge: 'Mailbox', - title: $rc === 0 ? 'Mailbox aktualisiert' : 'Aktualisierung fehlgeschlagen', - text: $rc === 0 ? 'Statistiken wurden aktualisiert.' : 'Siehe logs/mailstats.log', - duration: 6000, - ); - } - - - public function updateMailboxStatsOne(string $email) - { - Artisan::call('mail:update-stats', ['--user' => $email]); + Artisan::call('mail:update-stats'); $this->dispatch('$refresh'); $this->dispatch('toast', type: 'done', badge: 'Mailbox', title: 'Mailbox aktualisiert', text: 'Die Mailbox-Statistiken wurden aktualisiert.', - duration: 6000 + duration: 6000, ); } public function render() { - $system = Domain::query()->where('is_system', true)->first(); - $term = trim($this->search); - $hasTerm = $term !== ''; - $needle = '%'.str_replace(['%','_'], ['\%','\_'], $term).'%'; // LIKE-sicher + $system = Domain::query()->where('is_system', true)->first(); + $term = trim($this->search); + $hasTerm = $term !== ''; + $needle = '%' . str_replace(['%', '_'], ['\%', '\_'], $term) . '%'; $domains = Domain::query() - ->when($system, fn ($q) => $q->whereKeyNot($system->id)) - - // Domain selbst ODER MailUser müssen matchen + ->when($system, fn($q) => $q->whereKeyNot($system->id)) ->when($hasTerm, function ($q) use ($needle) { $q->where(function ($w) use ($needle) { $w->where('domain', 'like', $needle) ->orWhereHas('mailUsers', fn($u) => $u->where('localpart', 'like', $needle)); }); }) - ->withCount(['mailUsers']) - - // Relationen zunächst ggf. gefiltert laden ->with([ 'mailUsers' => function ($q) use ($hasTerm, $needle) { - if ($hasTerm) $q->where('localpart', 'like', $needle); + if ($hasTerm) { + $q->where('localpart', 'like', $needle); + } $q->orderBy('localpart'); }, ]) - ->orderBy('domain') ->get(); - // Wenn der Domainname selbst matched → alle Mailboxen/Aliasse vollständig nachladen if ($hasTerm) { $lower = Str::lower($term); foreach ($domains as $d) { if (Str::contains(Str::lower($d->domain), $lower)) { - $d->setRelation('mailUsers', $d->mailUsers()->orderBy('localpart')->get()); + $d->setRelation('mailUsers', $d->mailUsers()->orderBy('localpart')->get()); $d->setRelation('mailAliases', $d->mailAliases()->orderBy('local')->get()); } } } - // Vorbereitung für Blade + // ---- Anzeige vorbereiten (mit korrekter Prozent-Berechnung) ---- foreach ($domains as $d) { - $prepared = []; + $prepared = []; $domainActive = (bool)($d->is_active ?? true); foreach ($d->mailUsers as $u) { - $email = trim($u->email ?? '') !== '' - ? $u->email - : ($u->localpart !== '' ? ($u->localpart.'@'.$d->domain) : null); + // Stats aus Settings (Redis → DB Fallback) + $stats = Setting::get("mailbox.{$u->email}", []); + $usedBytes = (int)($stats['used_bytes'] ?? ($u->used_bytes ?? 0)); + $messageCount = (int)($stats['message_count'] ?? ($u->message_count ?? 0)); - $stats = $email ? Setting::get("mailbox.$email") : null; + // Anzeige in MiB (nur fürs UI runden) + $usedMiB = (float)round($usedBytes / 1048576, 2); + $quotaMiB = (int)($u->quota_mb ?? 0); - $usedBytes = is_array($stats) && isset($stats['used_bytes']) ? (int)$stats['used_bytes'] : (int)($u->used_bytes ?? 0); - $messageCount = is_array($stats) && isset($stats['message_count']) ? (int)$stats['message_count'] : (int)($u->message_count ?? 0); - - $usedMB = (int) round($usedBytes / 1024 / 1024); - $quota = (int)($u->quota_mb ?? 0); - $usage = $quota > 0 ? min(100, (int) round($usedMB / max(1, $quota) * 100)) : 0; + // Prozent aus Bytes/Quota (ohne Vorab-Rundung auf MiB!) + $usage = $quotaMiB > 0 + ? min(100, (int)round($usedBytes / ($quotaMiB * 1048576) * 100)) + : 0; $mailboxActive = (bool)($u->is_active ?? true); - $effective = $domainActive && $mailboxActive; + $effective = $domainActive && $mailboxActive; $reason = null; if (!$effective) { @@ -177,15 +137,15 @@ class MailboxList extends Component } $prepared[] = [ - 'id' => $u->id, - 'localpart' => (string)$u->localpart, - 'quota_mb' => $quota, - 'usage_percent' => $usage, - 'used_mb' => $usedMB, // MiB fürs UI - 'message_count' => $messageCount, - 'is_active' => $mailboxActive, + 'id' => $u->id, + 'localpart' => (string)$u->localpart, + 'quota_mb' => $quotaMiB, + 'used_mb' => $usedMiB, + 'usage_percent' => $usage, + 'message_count' => $messageCount, + 'is_active' => $mailboxActive, 'is_effective_active' => $effective, - 'inactive_reason' => $reason, + 'inactive_reason' => $reason, ]; } @@ -194,9 +154,116 @@ class MailboxList extends Component return view('livewire.ui.mail.mailbox-list', [ 'domains' => $domains, - 'system' => $this->showSystemCard ? $system : null, + 'system' => $this->showSystemCard ? $system : null, ]); } +} + +//namespace App\Livewire\Ui\Mail; +// +//use App\Models\Domain; +//use App\Models\Setting; +//use Illuminate\Support\Facades\Artisan; +//use Illuminate\Support\Facades\Log; +//use Illuminate\Support\Str; +//use Livewire\Attributes\On; +//use Livewire\Component; +// +//class MailboxList extends Component +//{ +// public string $search = ''; +// public bool $showSystemCard = false; +// +// #[On('mailbox:updated')] +// #[On('mailbox:deleted')] +// #[On('mailbox:created')] +// public function refreshMailboxList(): void +// { +// $this->dispatch('$refresh'); +// } +// +// #[On('focus:domain')] +// public function focusDomain(int $id): void +// { +// // z. B. Domain nach oben holen / scrollen / highlighten +// // oder direkt den "+ Postfach" Dialog: +// // $this->openMailboxCreate($id); +// } +// +// #[On('focus:user')] +// public function focusUser(int $id): void +// { +// // später: Benutzerseite / Filter setzen ... +// } +// +// public function openMailboxCreate(int $domainId): void +// { +// $this->dispatch('openModal', component: 'ui.mail.modal.mailbox-create-modal', arguments: [ +// 'domainId' => $domainId, +// ]); +// } +// +// public function openMailboxEdit(int $domainId): void +// { +// // $domainId == mailbox_id +// $this->dispatch('openModal', component: 'ui.mail.modal.mailbox-edit-modal', arguments: [ +// $domainId, // <— nur der Wert, kein Key! +// ]); +// } +// +// public function openMailboxDelete(int $domainId): void +// { +// $this->dispatch('openModal', component: 'ui.mail.modal.mailbox-delete-modal', arguments: [ +// $domainId, // <— nur der Wert, kein Key! +// ]); +// } +// +// public function updateMailboxStats() +// { +// $started = microtime(true); +// +// Log::channel('mailstats')->info('UI: updateMailboxStats() geklickt', [ +// 'actor' => 'web', +// 'ip' => request()->ip() ?? null, +// ]); +// +// // Command ausführen +// $rc = Artisan::call('mail:update-stats'); +// $output = Artisan::output(); +// +// Log::channel('mailstats')->info('UI: Command beendet', [ +// 'rc' => $rc, +// 'ms' => (int)((microtime(true) - $started) * 1000), +// 'output' => trim($output), +// ]); +// +// // UI auffrischen +// $this->dispatch('$refresh'); +// +// // Ergebnis toaster +// $this->dispatch('toast', +// type: $rc === 0 ? 'done' : 'warn', +// badge: 'Mailbox', +// title: $rc === 0 ? 'Mailbox aktualisiert' : 'Aktualisierung fehlgeschlagen', +// text: $rc === 0 ? 'Statistiken wurden aktualisiert.' : 'Siehe logs/mailstats.log', +// duration: 6000, +// ); +// } +// +// +// public function updateMailboxStatsOne(string $email) +// { +// Artisan::call('mail:update-stats', ['--user' => $email]); +// $this->dispatch('$refresh'); +// $this->dispatch('toast', +// type: 'done', +// badge: 'Mailbox', +// title: 'Mailbox aktualisiert', +// text: 'Die Mailbox-Statistiken wurden aktualisiert.', +// duration: 6000 +// ); +// } +// // public function render() // { // $system = Domain::query()->where('is_system', true)->first(); @@ -207,17 +274,17 @@ class MailboxList extends Component // $domains = Domain::query() // ->when($system, fn ($q) => $q->whereKeyNot($system->id)) // -// // Domain selbst ODER MailUser/ Aliasse müssen matchen +// // Domain selbst ODER MailUser müssen matchen // ->when($hasTerm, function ($q) use ($needle) { // $q->where(function ($w) use ($needle) { // $w->where('domain', 'like', $needle) -// ->orWhereHas('mailUsers', fn($u) => $u->where('localpart', 'like', $needle)); +// ->orWhereHas('mailUsers', fn($u) => $u->where('localpart', 'like', $needle)); // }); // }) // // ->withCount(['mailUsers']) // -// // Beziehungen zunächst gefiltert laden (damit "test" nur passende Mailboxen zeigt) +// // Relationen zunächst ggf. gefiltert laden // ->with([ // 'mailUsers' => function ($q) use ($hasTerm, $needle) { // if ($hasTerm) $q->where('localpart', 'like', $needle); @@ -228,35 +295,35 @@ class MailboxList extends Component // ->orderBy('domain') // ->get(); // -// // Domains, deren NAME den Suchbegriff trifft → ALLE Mailboxen/Aliasse zeigen +// // Wenn der Domainname selbst matched → alle Mailboxen/Aliasse vollständig nachladen // if ($hasTerm) { // $lower = Str::lower($term); // foreach ($domains as $d) { // if (Str::contains(Str::lower($d->domain), $lower)) { -// // volle Relationen nachladen (überschreibt die gefilterten) // $d->setRelation('mailUsers', $d->mailUsers()->orderBy('localpart')->get()); // $d->setRelation('mailAliases', $d->mailAliases()->orderBy('local')->get()); // } // } // } // -// // Vorbereitung für Blade (unverändert, arbeitet auf den ggf. gefilterten Relationen) +// // Vorbereitung für Blade // foreach ($domains as $d) { // $prepared = []; // $domainActive = (bool)($d->is_active ?? true); // // foreach ($d->mailUsers as $u) { -// $stats = Setting::get("mailbox.{$u->email}"); -// $usedBytes = $stats['used_bytes'] ?? ($u->used_bytes ?? 0); -// $messageCount = $stats['message_count'] ?? ($u->message_count ?? 0); -// $usedMB = (int) round(($usedBytes) / 1024 / 1024); +// $email = trim($u->email ?? '') !== '' +// ? $u->email +// : ($u->localpart !== '' ? ($u->localpart.'@'.$d->domain) : null); +// +// $stats = $email ? Setting::get("mailbox.$email") : null; +// +// $usedBytes = is_array($stats) && isset($stats['used_bytes']) ? (int)$stats['used_bytes'] : (int)($u->used_bytes ?? 0); +// $messageCount = is_array($stats) && isset($stats['message_count']) ? (int)$stats['message_count'] : (int)($u->message_count ?? 0); +// +// $usedMB = (int) round($usedBytes / 1024 / 1024); // $quota = (int)($u->quota_mb ?? 0); -// $usage = $quota > 0 ? min(100, (int) round($usedMB / max(1,$quota) * 100)) : 0; -// -// -//// $quota = (int)($u->quota_mb ?? 0); -//// $used = (int)($u->used_mb ?? 0); -//// $usage = $quota > 0 ? min(100, (int)round($used / max(1, $quota) * 100)) : 0; +// $usage = $quota > 0 ? min(100, (int) round($usedMB / max(1, $quota) * 100)) : 0; // // $mailboxActive = (bool)($u->is_active ?? true); // $effective = $domainActive && $mailboxActive; @@ -272,7 +339,7 @@ class MailboxList extends Component // 'localpart' => (string)$u->localpart, // 'quota_mb' => $quota, // 'usage_percent' => $usage, -// 'used_mb' => $usedMB, +// 'used_mb' => $usedMB, // MiB fürs UI // 'message_count' => $messageCount, // 'is_active' => $mailboxActive, // 'is_effective_active' => $effective, @@ -280,7 +347,6 @@ class MailboxList extends Component // ]; // } // -// // für Blade // $d->prepared_mailboxes = $prepared; // } // @@ -289,136 +355,228 @@ class MailboxList extends Component // 'system' => $this->showSystemCard ? $system : null, // ]); // } - -// public function render() -// { -// $system = Domain::query()->where('is_system', true)->first(); -// $term = trim($this->search); +//// public function render() +//// { +//// $system = Domain::query()->where('is_system', true)->first(); +//// $term = trim($this->search); +//// $hasTerm = $term !== ''; +//// $needle = '%'.str_replace(['%','_'], ['\%','\_'], $term).'%'; // LIKE-sicher +//// +//// $domains = Domain::query() +//// ->when($system, fn ($q) => $q->whereKeyNot($system->id)) +//// +//// // Domain selbst ODER MailUser/ Aliasse müssen matchen +//// ->when($hasTerm, function ($q) use ($needle) { +//// $q->where(function ($w) use ($needle) { +//// $w->where('domain', 'like', $needle) +//// ->orWhereHas('mailUsers', fn($u) => $u->where('localpart', 'like', $needle)); +//// }); +//// }) +//// +//// ->withCount(['mailUsers']) +//// +//// // Beziehungen zunächst gefiltert laden (damit "test" nur passende Mailboxen zeigt) +//// ->with([ +//// 'mailUsers' => function ($q) use ($hasTerm, $needle) { +//// if ($hasTerm) $q->where('localpart', 'like', $needle); +//// $q->orderBy('localpart'); +//// }, +//// ]) +//// +//// ->orderBy('domain') +//// ->get(); +//// +//// // Domains, deren NAME den Suchbegriff trifft → ALLE Mailboxen/Aliasse zeigen +//// if ($hasTerm) { +//// $lower = Str::lower($term); +//// foreach ($domains as $d) { +//// if (Str::contains(Str::lower($d->domain), $lower)) { +//// // volle Relationen nachladen (überschreibt die gefilterten) +//// $d->setRelation('mailUsers', $d->mailUsers()->orderBy('localpart')->get()); +//// $d->setRelation('mailAliases', $d->mailAliases()->orderBy('local')->get()); +//// } +//// } +//// } +//// +//// // Vorbereitung für Blade (unverändert, arbeitet auf den ggf. gefilterten Relationen) +//// foreach ($domains as $d) { +//// $prepared = []; +//// $domainActive = (bool)($d->is_active ?? true); +//// +//// foreach ($d->mailUsers as $u) { +//// $stats = Setting::get("mailbox.{$u->email}"); +//// $usedBytes = $stats['used_bytes'] ?? ($u->used_bytes ?? 0); +//// $messageCount = $stats['message_count'] ?? ($u->message_count ?? 0); +//// $usedMB = (int) round(($usedBytes) / 1024 / 1024); +//// $quota = (int)($u->quota_mb ?? 0); +//// $usage = $quota > 0 ? min(100, (int) round($usedMB / max(1,$quota) * 100)) : 0; +//// +//// +////// $quota = (int)($u->quota_mb ?? 0); +////// $used = (int)($u->used_mb ?? 0); +////// $usage = $quota > 0 ? min(100, (int)round($used / max(1, $quota) * 100)) : 0; +//// +//// $mailboxActive = (bool)($u->is_active ?? true); +//// $effective = $domainActive && $mailboxActive; +//// +//// $reason = null; +//// if (!$effective) { +//// $reason = !$domainActive ? 'Domain inaktiv' +//// : (!$mailboxActive ? 'Postfach inaktiv' : null); +//// } +//// +//// $prepared[] = [ +//// 'id' => $u->id, +//// 'localpart' => (string)$u->localpart, +//// 'quota_mb' => $quota, +//// 'usage_percent' => $usage, +//// 'used_mb' => $usedMB, +//// 'message_count' => $messageCount, +//// 'is_active' => $mailboxActive, +//// 'is_effective_active' => $effective, +//// 'inactive_reason' => $reason, +//// ]; +//// } +//// +//// // für Blade +//// $d->prepared_mailboxes = $prepared; +//// } +//// +//// return view('livewire.ui.mail.mailbox-list', [ +//// 'domains' => $domains, +//// 'system' => $this->showSystemCard ? $system : null, +//// ]); +//// } // -// $domains = Domain::query() -// ->when($system, fn ($q) => $q->where('id', '!=', $system->id)) -// ->withCount(['mailUsers','mailAliases']) -// ->with([ -// 'mailUsers' => fn ($q) => $q->orderBy('localpart'), -// 'mailAliases' => fn ($q) => $q->orderBy('local'), -// ]) -// ->when($term !== '', function ($q) use ($term) { -// $q->where(function ($w) use ($term) { -// $w->where('domain', 'like', "%{$term}%") -// ->orWhereHas('mailUsers', fn($u) => -// $u->where('localpart', 'like', "%{$term}%") -// ); -// }); -// }) -// ->orderBy('domain') -// ->get(); +//// public function render() +//// { +//// $system = Domain::query()->where('is_system', true)->first(); +//// $term = trim($this->search); +//// +//// $domains = Domain::query() +//// ->when($system, fn ($q) => $q->where('id', '!=', $system->id)) +//// ->withCount(['mailUsers','mailAliases']) +//// ->with([ +//// 'mailUsers' => fn ($q) => $q->orderBy('localpart'), +//// 'mailAliases' => fn ($q) => $q->orderBy('local'), +//// ]) +//// ->when($term !== '', function ($q) use ($term) { +//// $q->where(function ($w) use ($term) { +//// $w->where('domain', 'like', "%{$term}%") +//// ->orWhereHas('mailUsers', fn($u) => +//// $u->where('localpart', 'like', "%{$term}%") +//// ); +//// }); +//// }) +//// ->orderBy('domain') +//// ->get(); +//// +//// // Vorbereitung für Blade (unverändert) +//// foreach ($domains as $d) { +//// $prepared = []; +//// $domainActive = (bool)($d->is_active ?? true); +//// +//// foreach ($d->mailUsers as $u) { +//// $quota = (int) ($u->quota_mb ?? 0); +//// $used = (int) ($u->used_mb ?? 0); +//// $usage = $quota > 0 ? min(100, (int) round($used / max(1,$quota) * 100)) : 0; +//// +//// $mailboxActive = (bool)($u->is_active ?? true); +//// $effective = $domainActive && $mailboxActive; +//// +//// $reason = null; +//// if (!$effective) { +//// $reason = !$domainActive ? 'Domain inaktiv' +//// : (!$mailboxActive ? 'Postfach inaktiv' : null); +//// } +//// +//// $prepared[] = [ +//// 'id' => $u->id, +//// 'localpart' => (string) $u->localpart, +//// 'quota_mb' => $quota, +//// 'used_mb' => $used, +//// 'usage_percent' => $usage, +//// 'message_count' => (int) ($u->message_count ?? $u->mails_count ?? 0), +//// 'is_active' => $mailboxActive, +//// 'is_effective_active' => $effective, +//// 'inactive_reason' => $reason, +//// ]; +//// } +//// +//// $d->prepared_mailboxes = $prepared; +//// } +//// +//// return view('livewire.ui.mail.mailbox-list', [ +//// 'domains' => $domains, +//// 'system' => $this->showSystemCard ? $system : null, +//// ]); +//// } // -// // Vorbereitung für Blade (unverändert) -// foreach ($domains as $d) { -// $prepared = []; -// $domainActive = (bool)($d->is_active ?? true); -// -// foreach ($d->mailUsers as $u) { -// $quota = (int) ($u->quota_mb ?? 0); -// $used = (int) ($u->used_mb ?? 0); -// $usage = $quota > 0 ? min(100, (int) round($used / max(1,$quota) * 100)) : 0; -// -// $mailboxActive = (bool)($u->is_active ?? true); -// $effective = $domainActive && $mailboxActive; -// -// $reason = null; -// if (!$effective) { -// $reason = !$domainActive ? 'Domain inaktiv' -// : (!$mailboxActive ? 'Postfach inaktiv' : null); -// } -// -// $prepared[] = [ -// 'id' => $u->id, -// 'localpart' => (string) $u->localpart, -// 'quota_mb' => $quota, -// 'used_mb' => $used, -// 'usage_percent' => $usage, -// 'message_count' => (int) ($u->message_count ?? $u->mails_count ?? 0), -// 'is_active' => $mailboxActive, -// 'is_effective_active' => $effective, -// 'inactive_reason' => $reason, -// ]; -// } -// -// $d->prepared_mailboxes = $prepared; -// } -// -// return view('livewire.ui.mail.mailbox-list', [ -// 'domains' => $domains, -// 'system' => $this->showSystemCard ? $system : null, -// ]); -// } - -// public function render() -// { -// $system = Domain::query()->where('is_system', true)->first(); -// -// $term = trim($this->search); -// -// $domains = Domain::query() -// ->when($system, fn ($q) => $q->where('id', '!=', $system->id)) -// ->withCount(['mailUsers','mailAliases']) -// ->with([ -// 'mailUsers' => fn ($q) => $q->orderBy('localpart'), -// 'mailAliases' => fn ($q) => $q->orderBy('source'), -// ]) -// ->when($term !== '', function ($q) use ($term) { -// $q->where(function ($w) use ($term) { -// $w->where('domain', 'like', "%{$term}%") -// ->orWhereHas('mailUsers', fn($u) => -// $u->where('localpart', 'like', "%{$term}%") -// ); -// }); -// }) -// ->orderBy('domain') -// ->get(); -// -// // Für das Blade vorbereiten (ohne Relations zu mutieren) -// foreach ($domains as $d) { -// $prepared = []; -// $domainActive = (bool)($d->is_active ?? true); -// -// foreach ($d->mailUsers as $u) { -// $quota = (int) ($u->quota_mb ?? 0); -// $used = (int) ($u->used_mb ?? 0); -// $usage = $quota > 0 ? min(100, (int) round($used / max(1,$quota) * 100)) : 0; -// -// $mailboxActive = (bool)($u->is_active ?? true); -// $effective = $domainActive && $mailboxActive; -// -// $reason = null; -// if (!$effective) { -// $reason = !$domainActive ? 'Domain inaktiv' -// : (!$mailboxActive ? 'Postfach inaktiv' : null); -// } -// -// $prepared[] = [ -// 'id' => $u->id, -// 'localpart' => (string) $u->localpart, -// 'quota_mb' => $quota, -// 'used_mb' => $used, -// 'usage_percent' => $usage, -// 'message_count' => (int) ($u->message_count ?? $u->mails_count ?? 0), -// 'is_active' => $mailboxActive, // ursprünglicher Flag (falls du ihn brauchst) -// 'is_effective_active' => $effective, // ← NEU: Domain & Mailbox aktiv? -// 'inactive_reason' => $reason, // ← NEU: warum gesperrt -// ]; -// } -// -// $d->prepared_mailboxes = $prepared; -// } -// -// return view('livewire.ui.mail.mailbox-list', [ -// 'domains' => $domains, -// 'system' => $this->showSystemCard ? $system : null, -// ]); -// } -} +//// public function render() +//// { +//// $system = Domain::query()->where('is_system', true)->first(); +//// +//// $term = trim($this->search); +//// +//// $domains = Domain::query() +//// ->when($system, fn ($q) => $q->where('id', '!=', $system->id)) +//// ->withCount(['mailUsers','mailAliases']) +//// ->with([ +//// 'mailUsers' => fn ($q) => $q->orderBy('localpart'), +//// 'mailAliases' => fn ($q) => $q->orderBy('source'), +//// ]) +//// ->when($term !== '', function ($q) use ($term) { +//// $q->where(function ($w) use ($term) { +//// $w->where('domain', 'like', "%{$term}%") +//// ->orWhereHas('mailUsers', fn($u) => +//// $u->where('localpart', 'like', "%{$term}%") +//// ); +//// }); +//// }) +//// ->orderBy('domain') +//// ->get(); +//// +//// // Für das Blade vorbereiten (ohne Relations zu mutieren) +//// foreach ($domains as $d) { +//// $prepared = []; +//// $domainActive = (bool)($d->is_active ?? true); +//// +//// foreach ($d->mailUsers as $u) { +//// $quota = (int) ($u->quota_mb ?? 0); +//// $used = (int) ($u->used_mb ?? 0); +//// $usage = $quota > 0 ? min(100, (int) round($used / max(1,$quota) * 100)) : 0; +//// +//// $mailboxActive = (bool)($u->is_active ?? true); +//// $effective = $domainActive && $mailboxActive; +//// +//// $reason = null; +//// if (!$effective) { +//// $reason = !$domainActive ? 'Domain inaktiv' +//// : (!$mailboxActive ? 'Postfach inaktiv' : null); +//// } +//// +//// $prepared[] = [ +//// 'id' => $u->id, +//// 'localpart' => (string) $u->localpart, +//// 'quota_mb' => $quota, +//// 'used_mb' => $used, +//// 'usage_percent' => $usage, +//// 'message_count' => (int) ($u->message_count ?? $u->mails_count ?? 0), +//// 'is_active' => $mailboxActive, // ursprünglicher Flag (falls du ihn brauchst) +//// 'is_effective_active' => $effective, // ← NEU: Domain & Mailbox aktiv? +//// 'inactive_reason' => $reason, // ← NEU: warum gesperrt +//// ]; +//// } +//// +//// $d->prepared_mailboxes = $prepared; +//// } +//// +//// return view('livewire.ui.mail.mailbox-list', [ +//// 'domains' => $domains, +//// 'system' => $this->showSystemCard ? $system : null, +//// ]); +//// } +//}