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

main v1.0.105
boban 2025-10-31 00:23:48 +01:00
parent 4d1fd64158
commit ee44ff3def
2 changed files with 187 additions and 19 deletions

View File

@ -7,10 +7,9 @@ use Livewire\Component;
class Fail2BanCard extends Component
{
public bool $available = true; // fail2ban-client vorhanden?
public bool $permDenied = false; // Socket/Root-Rechte fehlen?
public int $activeBans = 0; // Summe gebannter IPs
/** @var array<int,array{name:string,banned:int,bantime:int}> */
public bool $available = true;
public bool $permDenied = false;
public int $activeBans = 0;
public array $jails = [];
public function mount(): void
@ -28,12 +27,13 @@ class Fail2BanCard extends Component
$this->load(true);
}
// Optional: öffnet später dein Detail-Modal/Tab
public function openDetails(string $jail): void
{
$this->dispatch('openModal', component: 'ui.security.modal.fail2-ban-jail-modal', arguments: ['jail' => $jail]);
// KORREKTER DISPATCH für wire-elements/modal
$this->dispatch('openModal', 'ui.security.modal.fail2ban-jail-modal', ['jail' => $jail]);
}
/* ---------------- intern ---------------- */
/* ------------------- intern ------------------- */
protected function load(bool $force = false): void
{
@ -46,7 +46,6 @@ class Fail2BanCard extends Component
return;
}
// Rechtecheck
[$ok, $raw] = $this->f2b('ping');
if (!$ok && stripos($raw, 'permission denied') !== false) {
$this->available = true;
@ -56,18 +55,16 @@ class Fail2BanCard extends Component
return;
}
// Jails laden
[, $status] = $this->f2b('status');
$jailsLn = $this->firstMatch('/Jail list:\s*(.+)$/mi', $status);
$jails = $jailsLn ? array_filter(array_map('trim', preg_split('/\s*,\s*/', $jailsLn))) : [];
$rows = [];
$sum = 0;
foreach ($jails as $j) {
[, $s] = $this->f2b('status ' . escapeshellarg($j));
$banned = (int)($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0);
$bantime = $this->getBantime($j); // Sek.; -1 = permanent
$bantime = $this->getBantime($j);
$rows[] = ['name' => $j, 'banned' => $banned, 'bantime' => $bantime];
$sum += $banned;
}
@ -78,15 +75,12 @@ class Fail2BanCard extends Component
$this->jails = $rows;
}
/** sudo + fail2ban-client ausführen; [ok, output] */
private function f2b(string $args): array
{
$sudo = '/usr/bin/sudo';
$f2b = '/usr/bin/fail2ban-client';
$out = (string)@shell_exec("timeout 2 $sudo -n $f2b $args 2>&1");
$ok = stripos($out, 'Status') !== false
|| stripos($out, 'Jail list') !== false
|| stripos($out, 'pong') !== false;
$ok = str_contains($out, 'Status') || str_contains($out, 'Jail list') || str_contains($out, 'pong');
return [$ok, $out];
}
@ -95,7 +89,7 @@ class Fail2BanCard extends Component
[, $out] = $this->f2b('get ' . escapeshellarg($jail) . ' bantime');
$val = trim($out);
if (preg_match('/-?\d+/', $val, $m)) return (int)$m[0];
return 600; // defensiver Default
return 600;
}
private function firstMatch(string $pattern, string $haystack): ?string
@ -105,6 +99,110 @@ class Fail2BanCard extends Component
}
//namespace App\Livewire\Ui\Security;
//
//use Livewire\Component;
//
//class Fail2BanCard extends Component
//{
// public bool $available = true; // fail2ban-client vorhanden?
// public bool $permDenied = false; // Socket/Root-Rechte fehlen?
// public int $activeBans = 0; // Summe gebannter IPs
// /** @var array<int,array{name:string,banned:int,bantime:int}> */
// public array $jails = [];
//
// public function mount(): void
// {
// $this->load();
// }
//
// public function render()
// {
// return view('livewire.ui.security.fail2-ban-card');
// }
//
// public function refresh(): void
// {
// $this->load(true);
// }
//
// // Optional: öffnet später dein Detail-Modal/Tab
// public function openDetails(string $jail): void
// {
// $this->dispatch('openModal', 'ui.security.modal.fail2-ban-jail-modal', ['jail' => $jail]);
// }
// /* ---------------- intern ---------------- */
//
// protected function load(bool $force = false): void
// {
// $bin = trim((string)@shell_exec('command -v fail2ban-client 2>/dev/null')) ?: '';
// if ($bin === '') {
// $this->available = false;
// $this->permDenied = false;
// $this->activeBans = 0;
// $this->jails = [];
// return;
// }
//
// // Rechtecheck
// [$ok, $raw] = $this->f2b('ping');
// if (!$ok && stripos($raw, 'permission denied') !== false) {
// $this->available = true;
// $this->permDenied = true;
// $this->activeBans = 0;
// $this->jails = [];
// return;
// }
//
// // Jails laden
// [, $status] = $this->f2b('status');
// $jailsLn = $this->firstMatch('/Jail list:\s*(.+)$/mi', $status);
// $jails = $jailsLn ? array_filter(array_map('trim', preg_split('/\s*,\s*/', $jailsLn))) : [];
//
// $rows = [];
// $sum = 0;
//
// foreach ($jails as $j) {
// [, $s] = $this->f2b('status ' . escapeshellarg($j));
// $banned = (int)($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0);
// $bantime = $this->getBantime($j); // Sek.; -1 = permanent
// $rows[] = ['name' => $j, 'banned' => $banned, 'bantime' => $bantime];
// $sum += $banned;
// }
//
// $this->available = true;
// $this->permDenied = false;
// $this->activeBans = $sum;
// $this->jails = $rows;
// }
//
// /** sudo + fail2ban-client ausführen; [ok, output] */
// private function f2b(string $args): array
// {
// $sudo = '/usr/bin/sudo';
// $f2b = '/usr/bin/fail2ban-client';
// $out = (string)@shell_exec("timeout 2 $sudo -n $f2b $args 2>&1");
// $ok = stripos($out, 'Status') !== false
// || stripos($out, 'Jail list') !== false
// || stripos($out, 'pong') !== false;
// return [$ok, $out];
// }
//
// private function getBantime(string $jail): int
// {
// [, $out] = $this->f2b('get ' . escapeshellarg($jail) . ' bantime');
// $val = trim($out);
// if (preg_match('/-?\d+/', $val, $m)) return (int)$m[0];
// return 600; // defensiver Default
// }
//
// private function firstMatch(string $pattern, string $haystack): ?string
// {
// return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
// }
//}
//namespace App\Livewire\Ui\Security;
//
//use Livewire\Component;

View File

@ -30,14 +30,22 @@
<div class="rounded-xl border border-white/10 bg-white/5 px-3 py-2">
<div class="flex items-center justify-between">
<div class="text-white/85 font-medium">{{ $j['name'] }}</div>
<div class="flex items-center gap-2">
<span class="text-[11px] text-white/60">
Bannzeit:
@if($j['bantime'] === -1)
permanent
@else
{{ $j['bantime'] }}s
@endif
</span>
<span class="px-2 py-0.5 rounded-full border text-[11px]
{{ $j['banned']>0 ? 'text-amber-200 border-amber-400/30 bg-amber-500/10' : 'text-white/60 border-white/20 bg-white/5' }}">
{{ $j['banned'] }} gebannt
</span>
{{-- Optional: Details öffnen (Tab/Modal) --}}
<button wire:click="openDetails('{{ $j['name'] }}')"
{{-- fix: stop event bubbling --}}
<button wire:click.stop="openDetails('{{ $j['name'] }}')"
class="text-[11px] px-2 py-0.5 rounded border border-white/15 bg-white/5 hover:bg-white/10">
Details
</button>
@ -79,6 +87,68 @@
{{-- @endif--}}
{{-- </div>--}}
{{-- @if(!$available)--}}
{{-- <div class="text-sm text-white/60">fail2ban-client wurde nicht gefunden.</div>--}}
{{-- @elseif($permDenied)--}}
{{-- <div class="text-sm text-amber-200">--}}
{{-- Keine Berechtigung auf <code class="font-mono">/var/run/fail2ban/fail2ban.sock</code>.--}}
{{-- <span class="opacity-80">Sudo-Regel prüfen.</span>--}}
{{-- </div>--}}
{{-- @else--}}
{{-- <div class="space-y-2">--}}
{{-- @forelse($jails as $j)--}}
{{-- <div class="rounded-xl border border-white/10 bg-white/5 px-3 py-2">--}}
{{-- <div class="flex items-center justify-between">--}}
{{-- <div class="text-white/85 font-medium">{{ $j['name'] }}</div>--}}
{{-- <div class="flex items-center gap-2">--}}
{{-- <span class="px-2 py-0.5 rounded-full border text-[11px]--}}
{{-- {{ $j['banned']>0 ? 'text-amber-200 border-amber-400/30 bg-amber-500/10' : 'text-white/60 border-white/20 bg-white/5' }}">--}}
{{-- {{ $j['banned'] }} gebannt--}}
{{-- </span>--}}
{{-- --}}{{-- Optional: Details öffnen (Tab/Modal) --}}
{{-- <button wire:click="openDetails('{{ $j['name'] }}')"--}}
{{-- class="text-[11px] px-2 py-0.5 rounded border border-white/15 bg-white/5 hover:bg-white/10">--}}
{{-- Details--}}
{{-- </button>--}}
{{-- </div>--}}
{{-- </div>--}}
{{-- </div>--}}
{{-- @empty--}}
{{-- <div class="text-sm text-white/60">Keine Jails gefunden.</div>--}}
{{-- @endforelse--}}
{{-- </div>--}}
{{-- <div class="mt-4 flex justify-end">--}}
{{-- <button wire:click="refresh" wire:loading.attr="disabled"--}}
{{-- class="px-3 py-1.5 text-[12px] rounded-lg bg-white/5 border border-white/10 hover:bg-white/10">--}}
{{-- <i class="ph ph-arrows-clockwise text-[13px]"></i>--}}
{{-- <span wire:loading.remove>Neu prüfen</span>--}}
{{-- <span wire:loading>prüfe…</span>--}}
{{-- </button>--}}
{{-- </div>--}}
{{-- @endif--}}
{{--</div>--}}
{{--<div 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-shield-checkered text-white/70 text-[13px]"></i>--}}
{{-- <span class="text-[11px] uppercase text-white/70">Fail2Ban</span>--}}
{{-- </div>--}}
{{-- @if($available)--}}
{{-- <span class="px-2 py-0.5 rounded-full border text-xs--}}
{{-- {{ $activeBans>0 ? 'text-amber-200 border-amber-400/30 bg-amber-500/10' : 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10' }}">--}}
{{-- {{ $activeBans }} aktuell--}}
{{-- </span>--}}
{{-- @else--}}
{{-- <span class="px-2 py-0.5 rounded-full border text-xs text-rose-300 border-rose-400/30 bg-rose-500/10">--}}
{{-- nicht installiert--}}
{{-- </span>--}}
{{-- @endif--}}
{{-- </div>--}}
{{-- @if(!$available)--}}
{{-- <div class="text-sm text-white/60">fail2ban-client wurde nicht gefunden.</div>--}}
{{-- @else--}}