diff --git a/app/Console/Commands/UpdateMailboxStats.php b/app/Console/Commands/UpdateMailboxStats.php index 1c57fcb..465de2e 100644 --- a/app/Console/Commands/UpdateMailboxStats.php +++ b/app/Console/Commands/UpdateMailboxStats.php @@ -3,6 +3,7 @@ namespace App\Console\Commands; use App\Models\MailUser; +use App\Models\Setting; use Illuminate\Console\Command; use RecursiveDirectoryIterator; use RecursiveIteratorIterator; @@ -58,12 +59,11 @@ class UpdateMailboxStats extends Command $messageCount = $this->messageCountViaFilesystem($maildir); } - // Update DB - $u->forceFill([ - 'used_bytes' => $usedBytes, - 'message_count' => (int) $messageCount, - 'stats_refreshed_at' => now(), - ])->save(); + Setting::set("mailbox.{$email}", [ + 'used_bytes' => $usedBytes, + 'message_count' => $messageCount, + 'updated_at' => now()->toDateTimeString(), + ]); $this->line(sprintf( "%-35s %7.1f MiB %5d msgs", @@ -127,7 +127,7 @@ class UpdateMailboxStats extends Command $count++; } } - closedir($dh); + closedir($dh); } } } diff --git a/app/Livewire/Ui/Mail/MailboxList.php b/app/Livewire/Ui/Mail/MailboxList.php index eedaa22..c378b7b 100644 --- a/app/Livewire/Ui/Mail/MailboxList.php +++ b/app/Livewire/Ui/Mail/MailboxList.php @@ -4,6 +4,7 @@ namespace App\Livewire\Ui\Mail; use App\Models\Domain; +use App\Models\Setting; use Illuminate\Support\Facades\Artisan; use Illuminate\Support\Str; use Livewire\Attributes\On; @@ -72,6 +73,19 @@ class MailboxList extends Component ); } + 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(); @@ -82,17 +96,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); @@ -103,32 +117,39 @@ 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) { - $usedMB = (int) round(($u->used_bytes ?? 0) / 1024 / 1024); + // E-Mail sicher bestimmen (Fallback aus localpart + Domain) + $email = $u->email ?? ($u->localpart ? ($u->localpart.'@'.$d->domain) : null); + + // Stats aus Redis/DB holen (kann array, json-string oder null sein) + $statsRaw = $email ? Setting::get("mailbox.$email") : null; + $stats = is_array($statsRaw) + ? $statsRaw + : (is_string($statsRaw) ? (json_decode($statsRaw, true) ?: null) : null); + + // Werte priorisieren: Setting → DB-Felder → 0 + $usedBytes = (int)($stats['used_bytes'] ?? ($u->used_bytes ?? 0)); + $messageCount = (int)($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; + $usage = $quota > 0 ? min(100, (int) round($usedMB / max(1, $quota) * 100)) : 0; $mailboxActive = (bool)($u->is_active ?? true); $effective = $domainActive && $mailboxActive; @@ -144,17 +165,14 @@ class MailboxList extends Component 'localpart' => (string)$u->localpart, 'quota_mb' => $quota, 'usage_percent' => $usage, - 'used_mb' => $usedMB, - 'message_count' => (int) ($u->message_count ?? 0), -// 'used_mb' => $used, -// 'message_count' => (int)($u->message_count ?? $u->mails_count ?? 0), + 'used_mb' => $usedMB, // MiB fürs UI + 'message_count' => $messageCount, 'is_active' => $mailboxActive, 'is_effective_active' => $effective, 'inactive_reason' => $reason, ]; } - // für Blade $d->prepared_mailboxes = $prepared; } @@ -163,6 +181,98 @@ class MailboxList extends Component 'system' => $this->showSystemCard ? $system : null, ]); } +// 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, +// ]); +// } // public function render() // { diff --git a/app/Livewire/Ui/System/UpdateManager.php b/app/Livewire/Ui/System/UpdateManager.php new file mode 100644 index 0000000..2149365 --- /dev/null +++ b/app/Livewire/Ui/System/UpdateManager.php @@ -0,0 +1,72 @@ +refreshState(); + // Optional: latest Tag/Version aus Cache anzeigen + $this->latest = cache('mailwolt.update_available'); + } + + public function refreshState(): void + { + $state = @trim(@file_get_contents('/var/lib/mailwolt/update/state') ?: ''); + $this->running = ($state === 'running'); + + $rcRaw = @trim(@file_get_contents('/var/lib/mailwolt/update/rc') ?: ''); + $this->rc = is_numeric($rcRaw) ? (int)$rcRaw : null; + + // letzte 200 Zeilen Log + $this->log = @shell_exec('tail -n 200 /var/log/mailwolt-update.log 2>/dev/null') ?? ''; + } + + public function runUpdate(): void + { + // Hinweis „Update verfügbar“ ausblenden + cache()->forget('mailwolt.update_available'); + + // Update im Hintergrund starten + @shell_exec('nohup sudo -n /usr/local/sbin/mw-update >/dev/null 2>&1 &'); + + $this->running = true; + $this->dispatch('toast', + type: 'info', + badge: 'Update', + title: 'Update gestartet', + text: 'Das System wird aktualisiert …', + duration: 6000 + ); + } + + public function poll(): void + { + $before = $this->running; + $this->refreshState(); + + if ($before && !$this->running && $this->rc !== null) { + $ok = ($this->rc === 0); + $this->dispatch('toast', + type: $ok ? 'done' : 'warn', + badge: 'Update', + title: $ok ? 'Update abgeschlossen' : 'Update fehlgeschlagen', + text: $ok ? 'Die neue Version ist aktiv.' : "Fehlercode: {$this->rc}. Log prüfen.", + duration: 8000 + ); + } + } + + public function render() + { + return view('livewire.ui.system.update-manager'); + } +} diff --git a/resources/views/components/partials/header.blade.php b/resources/views/components/partials/header.blade.php index 18cb40e..94c9560 100644 --- a/resources/views/components/partials/header.blade.php +++ b/resources/views/components/partials/header.blade.php @@ -9,20 +9,19 @@ - - @if ($latest = cache('mailwolt.update_available')) -