parent
8690067d9c
commit
46591669d6
|
|
@ -1,17 +1,18 @@
|
|||
<?php
|
||||
|
||||
|
||||
namespace App\Livewire\Ui\Security;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Livewire\Attributes\On;
|
||||
use Livewire\Component;
|
||||
|
||||
class Fail2BanCard extends Component
|
||||
{
|
||||
public bool $available = true;
|
||||
public bool $permDenied = false;
|
||||
public bool $available = true; // fail2ban-client vorhanden?
|
||||
public bool $permDenied = false; // sudo / Socket-Rechte fehlen?
|
||||
public bool $error = false; // anderer Fehler (Output unerwartet)
|
||||
public int $activeBans = 0;
|
||||
public array $jails = [];
|
||||
public array $jails = []; // [['name','banned','bantime','ips'=>[...]]]
|
||||
|
||||
public function mount(): void
|
||||
{
|
||||
|
|
@ -31,7 +32,7 @@ class Fail2BanCard extends Component
|
|||
|
||||
public function openDetails(string $jail): void
|
||||
{
|
||||
// KORREKTER DISPATCH für wire-elements/modal
|
||||
// wire-elements/modal (v2): Event-Namen + Component + Params
|
||||
$this->dispatch('openModal', component: 'ui.security.modal.fail2-ban-jail-modal', arguments: ['jail' => $jail]);
|
||||
}
|
||||
|
||||
|
|
@ -39,65 +40,76 @@ class Fail2BanCard extends Component
|
|||
|
||||
protected function load(bool $force = false): void
|
||||
{
|
||||
$this->available = true;
|
||||
$this->permDenied = false;
|
||||
$this->error = false;
|
||||
$this->activeBans = 0;
|
||||
$this->jails = [];
|
||||
|
||||
// existiert fail2ban-client?
|
||||
$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 = [];
|
||||
$this->available = false;
|
||||
return;
|
||||
}
|
||||
|
||||
// Rechte prüfen
|
||||
[$ok, $raw] = $this->f2b('ping');
|
||||
if (!$ok && stripos($raw, 'permission denied') !== false) {
|
||||
$this->available = true;
|
||||
// Rechte / Erreichbarkeit
|
||||
[, $ping] = $this->f2b('ping');
|
||||
if ($this->looksDenied($ping)) {
|
||||
$this->permDenied = true;
|
||||
$this->activeBans = 0;
|
||||
$this->jails = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Jail-Liste
|
||||
// Jails lesen
|
||||
[, $status] = $this->f2b('status');
|
||||
$jailsLn = $this->firstMatch('/Jail list:\s*(.+)$/mi', $status);
|
||||
$jails = $jailsLn ? array_filter(array_map('trim', preg_split('/\s*,\s*/', $jailsLn))) : [];
|
||||
if ($this->looksDenied($status)) {
|
||||
$this->permDenied = true;
|
||||
return;
|
||||
}
|
||||
if (!preg_match('/Jail list:\s*(.+)$/mi', $status, $mm)) {
|
||||
// etwas stimmt nicht – loggen und „error“ zeigen
|
||||
$this->error = true;
|
||||
Log::warning('Fail2BanCard: unexpected status output', ['status' => $status]);
|
||||
return;
|
||||
}
|
||||
|
||||
$jails = array_filter(array_map('trim', preg_split('/\s*,\s*/', $mm[1] ?? '')));
|
||||
$sum = 0;
|
||||
$rows = [];
|
||||
$sum = 0;
|
||||
|
||||
foreach ($jails as $j) {
|
||||
$jEsc = escapeshellarg($j);
|
||||
[, $s] = $this->f2b("status {$jEsc}");
|
||||
$banned = (int)($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0);
|
||||
$bantime = $this->getBantime($j);
|
||||
$ipListLine = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
||||
$ips = $ipListLine !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipListLine)))) : [];
|
||||
[, $s] = $this->f2b("status {$jEsc}");
|
||||
if ($this->looksDenied($s)) {
|
||||
$this->permDenied = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Details inkl. Restzeit je IP
|
||||
$ipDetails = $this->buildIpDetails($j, $ips, $bantime);
|
||||
$banned = (int)($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0);
|
||||
$bantime = $this->getBantime($j);
|
||||
$ipLine = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
||||
$ips = $ipLine !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipLine)))) : [];
|
||||
|
||||
$rows[] = [
|
||||
'name' => $j,
|
||||
'banned' => $banned,
|
||||
'bantime' => $bantime, // Sek. (-1 = permanent)
|
||||
'ips' => $ipDetails, // [['ip'=>..., 'remaining'=>..., 'until'=>...], ...]
|
||||
'name' => $j,
|
||||
'banned' => $banned,
|
||||
'bantime' => $bantime,
|
||||
// wir zeigen IPs NICHT mehr in der Card; Details sind im Modal
|
||||
'ips' => [],
|
||||
];
|
||||
$sum += $banned;
|
||||
}
|
||||
|
||||
$this->available = true;
|
||||
$this->permDenied = false;
|
||||
$this->activeBans = $sum;
|
||||
$this->jails = $rows;
|
||||
$this->jails = $rows;
|
||||
}
|
||||
|
||||
private function f2b(string $args): array
|
||||
{
|
||||
$sudo = '/usr/bin/sudo';
|
||||
$f2b = '/usr/bin/fail2ban-client';
|
||||
$cmd = "timeout 3 $sudo -n $f2b $args 2>&1";
|
||||
$out = (string)@shell_exec($cmd);
|
||||
$f2b = '/usr/bin/fail2ban-client';
|
||||
$cmd = "timeout 3 $sudo -n $f2b $args 2>&1";
|
||||
$out = (string)@shell_exec($cmd);
|
||||
|
||||
$ok = stripos($out, 'Status') !== false
|
||||
|| stripos($out, 'Jail list') !== false
|
||||
|
|
@ -106,81 +118,209 @@ class Fail2BanCard extends Component
|
|||
return [$ok, $out];
|
||||
}
|
||||
|
||||
/** konfig. Bantime des Jails in Sekunden (-1 = permanent) */
|
||||
private function getBantime(string $jail): int
|
||||
{
|
||||
[, $out] = $this->f2b('get '.escapeshellarg($jail).' bantime');
|
||||
[, $out] = $this->f2b('get ' . escapeshellarg($jail) . ' bantime');
|
||||
if ($this->looksDenied($out)) {
|
||||
$this->permDenied = true;
|
||||
return 600;
|
||||
}
|
||||
$val = trim($out);
|
||||
if (preg_match('/-?\d+/', $val, $m)) return (int)$m[0];
|
||||
return 600; // konservativer Fallback
|
||||
return 600;
|
||||
}
|
||||
|
||||
/** Letzten Ban-Zeitpunkt (Unix-Timestamp) aus /var/log/fail2ban.log ermitteln. */
|
||||
private function lastBanTimestamp(string $jail, string $ip): ?int
|
||||
private function looksDenied(string $out): bool
|
||||
{
|
||||
$file = '/var/log/fail2ban.log';
|
||||
if (!is_readable($file)) return null;
|
||||
|
||||
// nur das Ende der Datei lesen (Performance, auch bei Rotation groß genug wählen)
|
||||
$tailBytes = 400000; // 400 KB
|
||||
$size = @filesize($file) ?: 0;
|
||||
$seek = max(0, $size - $tailBytes);
|
||||
|
||||
$fh = @fopen($file, 'rb');
|
||||
if (!$fh) return null;
|
||||
if ($seek > 0) fseek($fh, $seek);
|
||||
$data = stream_get_contents($fh) ?: '';
|
||||
fclose($fh);
|
||||
|
||||
// Beispielzeile:
|
||||
// 2025-10-30 22:34:20,797 fail2ban.actions [...] NOTICE [sshd] Ban 193.46.255.244
|
||||
$j = preg_quote($jail, '/');
|
||||
$p = preg_quote($ip, '/');
|
||||
$pattern = '/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2}),\d+.*\['.$j.'\]\s+Ban\s+'.$p.'\s*$/m';
|
||||
|
||||
if (preg_match_all($pattern, $data, $m) && !empty($m[1])) {
|
||||
$date = end($m[1]); // YYYY-MM-DD
|
||||
$time = end($m[2]); // HH:MM:SS
|
||||
$dt = \DateTime::createFromFormat('Y-m-d H:i:s', "$date $time", new \DateTimeZone(date_default_timezone_get()));
|
||||
return $dt ? $dt->getTimestamp() : null;
|
||||
}
|
||||
return null;
|
||||
return preg_match('/(permission denied|not allowed to execute|a password is required)/i', $out) === 1;
|
||||
}
|
||||
|
||||
/** Baut Details inkl. Restzeit (Sekunden; -1 = permanent). */
|
||||
private function buildIpDetails(string $jail, array $ips, int $bantime): array
|
||||
{
|
||||
$now = time();
|
||||
$out = [];
|
||||
|
||||
foreach ($ips as $ip) {
|
||||
$banAt = $this->lastBanTimestamp($jail, $ip);
|
||||
$remaining = null;
|
||||
$until = null;
|
||||
|
||||
if ($bantime === -1) {
|
||||
$remaining = -1; // permanent
|
||||
} elseif ($banAt !== null) {
|
||||
$remaining = max(0, $bantime - ($now - $banAt));
|
||||
$until = $remaining > 0 ? ($banAt + $bantime) : null;
|
||||
}
|
||||
|
||||
$out[] = [
|
||||
'ip' => $ip,
|
||||
'remaining' => $remaining, // -1 = permanent, null = Ban-Zeitpunkt nicht gefunden, >=0 = Sekunden
|
||||
'until' => $until, // Unix-Timestamp oder null
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
|
||||
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\Attributes\On;
|
||||
//use Livewire\Component;
|
||||
//
|
||||
//class Fail2BanCard extends Component
|
||||
//{
|
||||
// public bool $available = true;
|
||||
// public bool $permDenied = false;
|
||||
// public int $activeBans = 0;
|
||||
// public array $jails = [];
|
||||
//
|
||||
// public function mount(): void
|
||||
// {
|
||||
// $this->load();
|
||||
// }
|
||||
//
|
||||
// public function render()
|
||||
// {
|
||||
// return view('livewire.ui.security.fail2-ban-card');
|
||||
// }
|
||||
//
|
||||
// #[On('f2b:refresh-banlist')]
|
||||
// public function refresh(): void
|
||||
// {
|
||||
// $this->load(true);
|
||||
// }
|
||||
//
|
||||
// public function openDetails(string $jail): void
|
||||
// {
|
||||
// // KORREKTER DISPATCH für wire-elements/modal
|
||||
// $this->dispatch('openModal', component: 'ui.security.modal.fail2-ban-jail-modal', arguments: ['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;
|
||||
// }
|
||||
//
|
||||
// // Rechte prüfen
|
||||
// [$ok, $raw] = $this->f2b('ping');
|
||||
// if (!$ok && stripos($raw, 'permission denied') !== false) {
|
||||
// $this->available = true;
|
||||
// $this->permDenied = true;
|
||||
// $this->activeBans = 0;
|
||||
// $this->jails = [];
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// // Jail-Liste
|
||||
// [, $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) {
|
||||
// $jEsc = escapeshellarg($j);
|
||||
// [, $s] = $this->f2b("status {$jEsc}");
|
||||
// $banned = (int)($this->firstMatch('/Currently banned:\s+(\d+)/i', $s) ?: 0);
|
||||
// $bantime = $this->getBantime($j);
|
||||
// $ipListLine = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
||||
// $ips = $ipListLine !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipListLine)))) : [];
|
||||
//
|
||||
// // Details inkl. Restzeit je IP
|
||||
// $ipDetails = $this->buildIpDetails($j, $ips, $bantime);
|
||||
//
|
||||
// $rows[] = [
|
||||
// 'name' => $j,
|
||||
// 'banned' => $banned,
|
||||
// 'bantime' => $bantime, // Sek. (-1 = permanent)
|
||||
// 'ips' => $ipDetails, // [['ip'=>..., 'remaining'=>..., 'until'=>...], ...]
|
||||
// ];
|
||||
// $sum += $banned;
|
||||
// }
|
||||
//
|
||||
// $this->available = true;
|
||||
// $this->permDenied = false;
|
||||
// $this->activeBans = $sum;
|
||||
// $this->jails = $rows;
|
||||
// }
|
||||
//
|
||||
// private function f2b(string $args): array
|
||||
// {
|
||||
// $sudo = '/usr/bin/sudo';
|
||||
// $f2b = '/usr/bin/fail2ban-client';
|
||||
// $cmd = "timeout 3 $sudo -n $f2b $args 2>&1";
|
||||
// $out = (string)@shell_exec($cmd);
|
||||
//
|
||||
// $ok = stripos($out, 'Status') !== false
|
||||
// || stripos($out, 'Jail list') !== false
|
||||
// || stripos($out, 'pong') !== false;
|
||||
//
|
||||
// return [$ok, $out];
|
||||
// }
|
||||
//
|
||||
// /** konfig. Bantime des Jails in Sekunden (-1 = permanent) */
|
||||
// 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; // konservativer Fallback
|
||||
// }
|
||||
//
|
||||
// /** Letzten Ban-Zeitpunkt (Unix-Timestamp) aus /var/log/fail2ban.log ermitteln. */
|
||||
// private function lastBanTimestamp(string $jail, string $ip): ?int
|
||||
// {
|
||||
// $file = '/var/log/fail2ban.log';
|
||||
// if (!is_readable($file)) return null;
|
||||
//
|
||||
// // nur das Ende der Datei lesen (Performance, auch bei Rotation groß genug wählen)
|
||||
// $tailBytes = 400000; // 400 KB
|
||||
// $size = @filesize($file) ?: 0;
|
||||
// $seek = max(0, $size - $tailBytes);
|
||||
//
|
||||
// $fh = @fopen($file, 'rb');
|
||||
// if (!$fh) return null;
|
||||
// if ($seek > 0) fseek($fh, $seek);
|
||||
// $data = stream_get_contents($fh) ?: '';
|
||||
// fclose($fh);
|
||||
//
|
||||
// // Beispielzeile:
|
||||
// // 2025-10-30 22:34:20,797 fail2ban.actions [...] NOTICE [sshd] Ban 193.46.255.244
|
||||
// $j = preg_quote($jail, '/');
|
||||
// $p = preg_quote($ip, '/');
|
||||
// $pattern = '/^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2}),\d+.*\['.$j.'\]\s+Ban\s+'.$p.'\s*$/m';
|
||||
//
|
||||
// if (preg_match_all($pattern, $data, $m) && !empty($m[1])) {
|
||||
// $date = end($m[1]); // YYYY-MM-DD
|
||||
// $time = end($m[2]); // HH:MM:SS
|
||||
// $dt = \DateTime::createFromFormat('Y-m-d H:i:s', "$date $time", new \DateTimeZone(date_default_timezone_get()));
|
||||
// return $dt ? $dt->getTimestamp() : null;
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// /** Baut Details inkl. Restzeit (Sekunden; -1 = permanent). */
|
||||
// private function buildIpDetails(string $jail, array $ips, int $bantime): array
|
||||
// {
|
||||
// $now = time();
|
||||
// $out = [];
|
||||
//
|
||||
// foreach ($ips as $ip) {
|
||||
// $banAt = $this->lastBanTimestamp($jail, $ip);
|
||||
// $remaining = null;
|
||||
// $until = null;
|
||||
//
|
||||
// if ($bantime === -1) {
|
||||
// $remaining = -1; // permanent
|
||||
// } elseif ($banAt !== null) {
|
||||
// $remaining = max(0, $bantime - ($now - $banAt));
|
||||
// $until = $remaining > 0 ? ($banAt + $bantime) : null;
|
||||
// }
|
||||
//
|
||||
// $out[] = [
|
||||
// 'ip' => $ip,
|
||||
// 'remaining' => $remaining, // -1 = permanent, null = Ban-Zeitpunkt nicht gefunden, >=0 = Sekunden
|
||||
// 'until' => $until, // Unix-Timestamp oder null
|
||||
// ];
|
||||
// }
|
||||
// return $out;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// private function firstMatch(string $pattern, string $haystack): ?string
|
||||
// {
|
||||
// return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
//namespace App\Livewire\Ui\Security;
|
||||
//
|
||||
|
|
|
|||
|
|
@ -7,18 +7,7 @@ use LivewireUI\Modal\ModalComponent;
|
|||
class Fail2BanJailModal extends ModalComponent
|
||||
{
|
||||
public string $jail = '';
|
||||
/** @var array<int,array{
|
||||
* ip:string,
|
||||
* bantime:int, // Sek.; -1 = permanent
|
||||
* banned_at:?int, // Unix-Timestamp
|
||||
* remaining:?int, // -1=permanent, 0..Sekunden, null=unbekannt
|
||||
* until:?int, // Unix-Timestamp oder null
|
||||
* time_text:string, // "HHh MMm SSs", "permanent", "—"
|
||||
* meta_text:string, // "seit … • bis …" oder "seit …" oder "—"
|
||||
* box_class:string // Tailwind-Klassen für BG/Border
|
||||
* }]
|
||||
*/
|
||||
public array $rows = [];
|
||||
public array $rows = []; // [{ip,bantime,banned_at,remaining,until,time_text,meta_text,box_class}]
|
||||
|
||||
public static function modalMaxWidth(): string
|
||||
{
|
||||
|
|
@ -31,16 +20,16 @@ class Fail2BanJailModal extends ModalComponent
|
|||
$this->load();
|
||||
}
|
||||
|
||||
public function refresh(): void
|
||||
{
|
||||
$this->load(true);
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('livewire.ui.security.modal.fail2-ban-jail-modal');
|
||||
}
|
||||
|
||||
public function refresh(): void
|
||||
{
|
||||
$this->load(true);
|
||||
}
|
||||
|
||||
/* ---------------- intern ---------------- */
|
||||
|
||||
protected function load(bool $force = false): void
|
||||
|
|
@ -48,11 +37,10 @@ class Fail2BanJailModal extends ModalComponent
|
|||
$jail = $this->jail;
|
||||
|
||||
// Aktuell gebannte IPs
|
||||
[, $s] = $this->f2b('status '.escapeshellarg($jail));
|
||||
[, $s] = $this->f2b('status ' . escapeshellarg($jail));
|
||||
$ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
||||
$ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : [];
|
||||
|
||||
// bantime für Jail
|
||||
$bantime = $this->getBantime($jail);
|
||||
|
||||
$rows = [];
|
||||
|
|
@ -61,24 +49,22 @@ class Fail2BanJailModal extends ModalComponent
|
|||
|
||||
$remaining = null;
|
||||
$until = null;
|
||||
if ($banAt !== null) {
|
||||
if ($bantime === -1) {
|
||||
$remaining = -1;
|
||||
} else {
|
||||
$remaining = max(0, $bantime - (time() - $banAt));
|
||||
$until = $remaining > 0 ? $banAt + $bantime : null;
|
||||
}
|
||||
|
||||
if ($bantime === -1) {
|
||||
$remaining = -1;
|
||||
} elseif ($banAt !== null) {
|
||||
$remaining = max(0, $bantime - (time() - $banAt));
|
||||
$until = $remaining > 0 ? ($banAt + $bantime) : null;
|
||||
}
|
||||
|
||||
// Darstellung
|
||||
[$timeText, $metaText, $boxClass] = $this->present($remaining, $banAt, $until);
|
||||
|
||||
$rows[] = [
|
||||
'ip' => $ip,
|
||||
'bantime' => $bantime,
|
||||
'ip' => $ip,
|
||||
'bantime' => $bantime,
|
||||
'banned_at' => $banAt,
|
||||
'remaining' => $remaining,
|
||||
'until' => $until,
|
||||
'until' => $until,
|
||||
'time_text' => $timeText,
|
||||
'meta_text' => $metaText,
|
||||
'box_class' => $boxClass,
|
||||
|
|
@ -88,107 +74,13 @@ class Fail2BanJailModal extends ModalComponent
|
|||
$this->rows = $rows;
|
||||
}
|
||||
|
||||
/** letzte "Ban <IP>"-Zeile -> Unix-Timestamp (robust über zgrep/journal/jail-tail) */
|
||||
private function lastBanTimestamp(string $jail, string $ip): ?int
|
||||
{
|
||||
$sudo = '/usr/bin/sudo';
|
||||
$zgrep = '/usr/bin/zgrep';
|
||||
$journal = '/usr/bin/journalctl';
|
||||
$tail = '/usr/bin/tail';
|
||||
|
||||
$needle = sprintf('[%s] Ban %s', $jail, $ip);
|
||||
$q = escapeshellarg($needle);
|
||||
|
||||
// 1) zgrep über alle fail2ban-Logs (inkl. Rotation .N, .gz)
|
||||
$cmd1 = "$sudo -n $zgrep -h $q /var/log/fail2ban.log* 2>/dev/null | $tail -n 1";
|
||||
$line = trim((string)@shell_exec($cmd1));
|
||||
|
||||
// 2) Fallback: journald (ohne grep-Pipe, mit -g)
|
||||
if ($line === '') {
|
||||
$cmd2 = "$sudo -n $journal -u fail2ban --since '14 days ago' --no-pager -g $q 2>/dev/null | $tail -n 1";
|
||||
$line = trim((string)@shell_exec($cmd2));
|
||||
}
|
||||
|
||||
if ($line === '') return null;
|
||||
|
||||
// Zeitstempel extrahieren (YYYY-MM-DD HH:MM:SS)
|
||||
if (preg_match('/(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}:\d{2})/', $line, $m)) {
|
||||
$ts = strtotime($m[1].' '.$m[2]);
|
||||
return $ts ?: null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** baut Details inkl. Restzeit; wenn kein Timestamp gefunden: ~bantime als Approximation */
|
||||
private function buildIpDetails(string $jail, array $ips, int $bantime): array
|
||||
{
|
||||
$now = time();
|
||||
$out = [];
|
||||
|
||||
foreach ($ips as $ip) {
|
||||
$banAt = $this->lastBanTimestamp($jail, $ip);
|
||||
$remaining = null; $until = null;
|
||||
|
||||
if ($bantime === -1) {
|
||||
$remaining = -1;
|
||||
} elseif ($banAt !== null) {
|
||||
$remaining = max(0, $bantime - ($now - $banAt));
|
||||
$until = $remaining > 0 ? ($banAt + $bantime) : null;
|
||||
} else {
|
||||
// kein Ban-Timestamp gefunden → Approximation anzeigen
|
||||
$remaining = -2; // Kennzeichen für "~bantime"
|
||||
}
|
||||
|
||||
$out[] = [
|
||||
'ip' => $ip,
|
||||
'banned_at' => $banAt,
|
||||
'remaining' => $remaining, // -1=permanent, -2≈approx, 0..Sek, null=unbekannt
|
||||
'until' => $until,
|
||||
];
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
/** Darstellung (permanent / Restzeit / abgelaufen / approx / unbekannt) */
|
||||
private function present(?int $remaining, ?int $banAt, ?int $until): array
|
||||
{
|
||||
$green = 'border-emerald-400/30 bg-emerald-500/10';
|
||||
$amber = 'border-amber-400/30 bg-amber-500/10';
|
||||
$rose = 'border-rose-400/30 bg-rose-500/10';
|
||||
$muted = 'border-white/10 bg-white/5';
|
||||
|
||||
if ($remaining === -1) {
|
||||
return ['permanent', $banAt ? ('seit '.$this->fmtTs($banAt)) : '—', $rose];
|
||||
}
|
||||
if ($remaining === -2) {
|
||||
// Approximation: Bannzeit ohne Startzeitpunkt
|
||||
return ['~ '.$this->fmtSecs((int) $this->getApproxBantime()), '—', $amber];
|
||||
}
|
||||
if (is_int($remaining)) {
|
||||
if ($remaining > 0) {
|
||||
$meta = [];
|
||||
if ($banAt) $meta[] = 'seit '.$this->fmtTs($banAt);
|
||||
if ($until) $meta[] = 'bis '.$this->fmtTs($until);
|
||||
return [$this->fmtSecs($remaining), $meta ? implode(' • ', $meta) : '—', $amber];
|
||||
}
|
||||
return ['abgelaufen', $banAt ? ('seit '.$this->fmtTs($banAt)) : '—', $muted];
|
||||
}
|
||||
return ['—', '—', $muted];
|
||||
}
|
||||
|
||||
// Bantime als Approximation (du kannst das einfach aus dem aufrufenden Kontext übergeben;
|
||||
// hier fallback 600)
|
||||
private function getApproxBantime(): int { return 600; }
|
||||
|
||||
/** Darstellung ableiten (Farbcode + Texte) */
|
||||
|
||||
/** sudo + fail2ban-client */
|
||||
private function f2b(string $args): array
|
||||
{
|
||||
$sudo = '/usr/bin/sudo';
|
||||
$f2b = '/usr/bin/fail2ban-client';
|
||||
$out = (string) @shell_exec("timeout 3 $sudo -n $f2b $args 2>&1");
|
||||
$ok = stripos($out, 'Status') !== false
|
||||
$f2b = '/usr/bin/fail2ban-client';
|
||||
$out = (string)@shell_exec("timeout 3 $sudo -n $f2b $args 2>&1");
|
||||
$ok = stripos($out, 'Status') !== false
|
||||
|| stripos($out, 'Jail list') !== false
|
||||
|| stripos($out, 'pong') !== false;
|
||||
return [$ok, $out];
|
||||
|
|
@ -196,24 +88,68 @@ class Fail2BanJailModal extends ModalComponent
|
|||
|
||||
private function getBantime(string $jail): int
|
||||
{
|
||||
[, $out] = $this->f2b('get '.escapeshellarg($jail).' bantime');
|
||||
[, $out] = $this->f2b('get ' . escapeshellarg($jail) . ' bantime');
|
||||
$val = trim($out);
|
||||
if (preg_match('/-?\d+/', $val, $m)) return (int)$m[0];
|
||||
return 600;
|
||||
}
|
||||
|
||||
/** letzte "Ban <IP>"-Zeile parsen -> Unix-Timestamp */
|
||||
/** letzte "Ban <IP>"-Zeile -> Unix-Timestamp aus /var/log/fail2ban.log */
|
||||
/** robust: zgrep + journalctl via sudo */
|
||||
private function lastBanTimestamp(string $jail, string $ip): ?int
|
||||
{
|
||||
$sudo = '/usr/bin/sudo';
|
||||
$zgrep = '/usr/bin/zgrep';
|
||||
$journal = '/usr/bin/journalctl';
|
||||
$tail = '/usr/bin/tail';
|
||||
|
||||
$needle = sprintf('[%s] Ban %s', $jail, $ip);
|
||||
$q = escapeshellarg($needle);
|
||||
|
||||
// Logs inkl. Rotation (gz)
|
||||
$cmd1 = "$sudo -n $zgrep -h $q /var/log/fail2ban.log* 2>/dev/null | $tail -n 1";
|
||||
$line = trim((string)@shell_exec($cmd1));
|
||||
|
||||
if ($line === '') { // Fallback: journald
|
||||
$cmd2 = "$sudo -n $journal -u fail2ban --since '14 days ago' --no-pager -g $q 2>/dev/null | $tail -n 1";
|
||||
$line = trim((string)@shell_exec($cmd2));
|
||||
}
|
||||
if ($line === '') return null;
|
||||
|
||||
if (preg_match('/(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}:\d{2})/', $line, $m)) {
|
||||
$ts = strtotime($m[1] . ' ' . $m[2]);
|
||||
return $ts ?: null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function firstMatch(string $pattern, string $haystack): ?string
|
||||
{
|
||||
return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
||||
}
|
||||
|
||||
private function present(?int $remaining, ?int $banAt, ?int $until): array
|
||||
{
|
||||
$amber = 'border-amber-400/30 bg-amber-500/10';
|
||||
$rose = 'border-rose-400/30 bg-rose-500/10';
|
||||
$muted = 'border-white/10 bg-white/5';
|
||||
|
||||
if ($remaining === -1) {
|
||||
return ['permanent', $banAt ? ('seit ' . $this->fmtTs($banAt)) : '—', $rose];
|
||||
}
|
||||
if (is_int($remaining)) {
|
||||
if ($remaining > 0) {
|
||||
$meta = [];
|
||||
if ($banAt) $meta[] = 'seit ' . $this->fmtTs($banAt);
|
||||
if ($until) $meta[] = 'bis ' . $this->fmtTs($until);
|
||||
return [$this->fmtSecs($remaining), $meta ? implode(' • ', $meta) : '—', $amber];
|
||||
}
|
||||
return ['abgelaufen', $banAt ? ('seit ' . $this->fmtTs($banAt)) : '—', $muted];
|
||||
}
|
||||
return ['—', '—', $muted];
|
||||
}
|
||||
|
||||
private function fmtSecs(int $s): string
|
||||
{
|
||||
// kompakt: 1h 23m 45s / 05m 10s / 12s
|
||||
$h = intdiv($s, 3600);
|
||||
$m = intdiv($s % 3600, 60);
|
||||
$r = $s % 60;
|
||||
|
|
@ -224,7 +160,236 @@ class Fail2BanJailModal extends ModalComponent
|
|||
|
||||
private function fmtTs(int $ts): string
|
||||
{
|
||||
// 29.10. 18:07
|
||||
return date('d.m. H:i', $ts);
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
//namespace App\Livewire\Ui\Security\Modal;
|
||||
//
|
||||
//use LivewireUI\Modal\ModalComponent;
|
||||
//
|
||||
//class Fail2BanJailModal extends ModalComponent
|
||||
//{
|
||||
// public string $jail = '';
|
||||
// /** @var array<int,array{
|
||||
// * ip:string,
|
||||
// * bantime:int, // Sek.; -1 = permanent
|
||||
// * banned_at:?int, // Unix-Timestamp
|
||||
// * remaining:?int, // -1=permanent, 0..Sekunden, null=unbekannt
|
||||
// * until:?int, // Unix-Timestamp oder null
|
||||
// * time_text:string, // "HHh MMm SSs", "permanent", "—"
|
||||
// * meta_text:string, // "seit … • bis …" oder "seit …" oder "—"
|
||||
// * box_class:string // Tailwind-Klassen für BG/Border
|
||||
// * }]
|
||||
// */
|
||||
// public array $rows = [];
|
||||
//
|
||||
// public static function modalMaxWidth(): string
|
||||
// {
|
||||
// return '4xl';
|
||||
// }
|
||||
//
|
||||
// public function mount(string $jail): void
|
||||
// {
|
||||
// $this->jail = $jail;
|
||||
// $this->load();
|
||||
// }
|
||||
//
|
||||
// public function refresh(): void
|
||||
// {
|
||||
// $this->load(true);
|
||||
// }
|
||||
//
|
||||
// public function render()
|
||||
// {
|
||||
// return view('livewire.ui.security.modal.fail2-ban-jail-modal');
|
||||
// }
|
||||
//
|
||||
// /* ---------------- intern ---------------- */
|
||||
//
|
||||
// protected function load(bool $force = false): void
|
||||
// {
|
||||
// $jail = $this->jail;
|
||||
//
|
||||
// // Aktuell gebannte IPs
|
||||
// [, $s] = $this->f2b('status '.escapeshellarg($jail));
|
||||
// $ipList = $this->firstMatch('/Banned IP list:\s*(.+)$/mi', $s) ?: '';
|
||||
// $ips = $ipList !== '' ? array_values(array_filter(array_map('trim', preg_split('/\s+/', $ipList)))) : [];
|
||||
//
|
||||
// // bantime für Jail
|
||||
// $bantime = $this->getBantime($jail);
|
||||
//
|
||||
// $rows = [];
|
||||
// foreach ($ips as $ip) {
|
||||
// $banAt = $this->lastBanTimestamp($jail, $ip); // Unix-Timestamp oder null
|
||||
//
|
||||
// $remaining = null;
|
||||
// $until = null;
|
||||
// if ($banAt !== null) {
|
||||
// if ($bantime === -1) {
|
||||
// $remaining = -1;
|
||||
// } else {
|
||||
// $remaining = max(0, $bantime - (time() - $banAt));
|
||||
// $until = $remaining > 0 ? $banAt + $bantime : null;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Darstellung
|
||||
// [$timeText, $metaText, $boxClass] = $this->present($remaining, $banAt, $until);
|
||||
//
|
||||
// $rows[] = [
|
||||
// 'ip' => $ip,
|
||||
// 'bantime' => $bantime,
|
||||
// 'banned_at' => $banAt,
|
||||
// 'remaining' => $remaining,
|
||||
// 'until' => $until,
|
||||
// 'time_text' => $timeText,
|
||||
// 'meta_text' => $metaText,
|
||||
// 'box_class' => $boxClass,
|
||||
// ];
|
||||
// }
|
||||
//
|
||||
// $this->rows = $rows;
|
||||
// }
|
||||
//
|
||||
// /** letzte "Ban <IP>"-Zeile -> Unix-Timestamp (robust über zgrep/journal/jail-tail) */
|
||||
// private function lastBanTimestamp(string $jail, string $ip): ?int
|
||||
// {
|
||||
// $sudo = '/usr/bin/sudo';
|
||||
// $zgrep = '/usr/bin/zgrep';
|
||||
// $journal = '/usr/bin/journalctl';
|
||||
// $tail = '/usr/bin/tail';
|
||||
//
|
||||
// $needle = sprintf('[%s] Ban %s', $jail, $ip);
|
||||
// $q = escapeshellarg($needle);
|
||||
//
|
||||
// // 1) zgrep über alle fail2ban-Logs (inkl. Rotation .N, .gz)
|
||||
// $cmd1 = "$sudo -n $zgrep -h $q /var/log/fail2ban.log* 2>/dev/null | $tail -n 1";
|
||||
// $line = trim((string)@shell_exec($cmd1));
|
||||
//
|
||||
// // 2) Fallback: journald (ohne grep-Pipe, mit -g)
|
||||
// if ($line === '') {
|
||||
// $cmd2 = "$sudo -n $journal -u fail2ban --since '14 days ago' --no-pager -g $q 2>/dev/null | $tail -n 1";
|
||||
// $line = trim((string)@shell_exec($cmd2));
|
||||
// }
|
||||
//
|
||||
// if ($line === '') return null;
|
||||
//
|
||||
// // Zeitstempel extrahieren (YYYY-MM-DD HH:MM:SS)
|
||||
// if (preg_match('/(\d{4}-\d{2}-\d{2})[ T](\d{2}:\d{2}:\d{2})/', $line, $m)) {
|
||||
// $ts = strtotime($m[1].' '.$m[2]);
|
||||
// return $ts ?: null;
|
||||
// }
|
||||
// return null;
|
||||
// }
|
||||
//
|
||||
// /** baut Details inkl. Restzeit; wenn kein Timestamp gefunden: ~bantime als Approximation */
|
||||
// private function buildIpDetails(string $jail, array $ips, int $bantime): array
|
||||
// {
|
||||
// $now = time();
|
||||
// $out = [];
|
||||
//
|
||||
// foreach ($ips as $ip) {
|
||||
// $banAt = $this->lastBanTimestamp($jail, $ip);
|
||||
// $remaining = null; $until = null;
|
||||
//
|
||||
// if ($bantime === -1) {
|
||||
// $remaining = -1;
|
||||
// } elseif ($banAt !== null) {
|
||||
// $remaining = max(0, $bantime - ($now - $banAt));
|
||||
// $until = $remaining > 0 ? ($banAt + $bantime) : null;
|
||||
// } else {
|
||||
// // kein Ban-Timestamp gefunden → Approximation anzeigen
|
||||
// $remaining = -2; // Kennzeichen für "~bantime"
|
||||
// }
|
||||
//
|
||||
// $out[] = [
|
||||
// 'ip' => $ip,
|
||||
// 'banned_at' => $banAt,
|
||||
// 'remaining' => $remaining, // -1=permanent, -2≈approx, 0..Sek, null=unbekannt
|
||||
// 'until' => $until,
|
||||
// ];
|
||||
// }
|
||||
// return $out;
|
||||
// }
|
||||
//
|
||||
// /** Darstellung (permanent / Restzeit / abgelaufen / approx / unbekannt) */
|
||||
// private function present(?int $remaining, ?int $banAt, ?int $until): array
|
||||
// {
|
||||
// $green = 'border-emerald-400/30 bg-emerald-500/10';
|
||||
// $amber = 'border-amber-400/30 bg-amber-500/10';
|
||||
// $rose = 'border-rose-400/30 bg-rose-500/10';
|
||||
// $muted = 'border-white/10 bg-white/5';
|
||||
//
|
||||
// if ($remaining === -1) {
|
||||
// return ['permanent', $banAt ? ('seit '.$this->fmtTs($banAt)) : '—', $rose];
|
||||
// }
|
||||
// if ($remaining === -2) {
|
||||
// // Approximation: Bannzeit ohne Startzeitpunkt
|
||||
// return ['~ '.$this->fmtSecs((int) $this->getApproxBantime()), '—', $amber];
|
||||
// }
|
||||
// if (is_int($remaining)) {
|
||||
// if ($remaining > 0) {
|
||||
// $meta = [];
|
||||
// if ($banAt) $meta[] = 'seit '.$this->fmtTs($banAt);
|
||||
// if ($until) $meta[] = 'bis '.$this->fmtTs($until);
|
||||
// return [$this->fmtSecs($remaining), $meta ? implode(' • ', $meta) : '—', $amber];
|
||||
// }
|
||||
// return ['abgelaufen', $banAt ? ('seit '.$this->fmtTs($banAt)) : '—', $muted];
|
||||
// }
|
||||
// return ['—', '—', $muted];
|
||||
// }
|
||||
//
|
||||
//// Bantime als Approximation (du kannst das einfach aus dem aufrufenden Kontext übergeben;
|
||||
//// hier fallback 600)
|
||||
// private function getApproxBantime(): int { return 600; }
|
||||
//
|
||||
// /** Darstellung ableiten (Farbcode + Texte) */
|
||||
//
|
||||
// /** sudo + fail2ban-client */
|
||||
// private function f2b(string $args): array
|
||||
// {
|
||||
// $sudo = '/usr/bin/sudo';
|
||||
// $f2b = '/usr/bin/fail2ban-client';
|
||||
// $out = (string) @shell_exec("timeout 3 $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;
|
||||
// }
|
||||
//
|
||||
// /** letzte "Ban <IP>"-Zeile parsen -> Unix-Timestamp */
|
||||
// /** letzte "Ban <IP>"-Zeile -> Unix-Timestamp aus /var/log/fail2ban.log */
|
||||
//
|
||||
// private function firstMatch(string $pattern, string $haystack): ?string
|
||||
// {
|
||||
// return preg_match($pattern, $haystack, $m) ? trim($m[1]) : null;
|
||||
// }
|
||||
//
|
||||
//
|
||||
// private function fmtSecs(int $s): string
|
||||
// {
|
||||
// // kompakt: 1h 23m 45s / 05m 10s / 12s
|
||||
// $h = intdiv($s, 3600);
|
||||
// $m = intdiv($s % 3600, 60);
|
||||
// $r = $s % 60;
|
||||
// if ($h > 0) return sprintf('%dh %02dm %02ds', $h, $m, $r);
|
||||
// if ($m > 0) return sprintf('%02dm %02ds', $m, $r);
|
||||
// return sprintf('%ds', $r);
|
||||
// }
|
||||
//
|
||||
// private function fmtTs(int $ts): string
|
||||
// {
|
||||
// // 29.10. 18:07
|
||||
// return date('d.m. H:i', $ts);
|
||||
// }
|
||||
//}
|
||||
|
|
|
|||
|
|
@ -19,11 +19,19 @@
|
|||
|
||||
@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>.
|
||||
Keine Berechtigung (sudo) auf <code class="font-mono">fail2ban-client</code>/<code class="font-mono">journalctl</code>/<code class="font-mono">zgrep</code>.
|
||||
<span class="opacity-80">Sudo-Regel prüfen.</span>
|
||||
</div>
|
||||
|
||||
@elseif($error)
|
||||
<div class="text-sm text-amber-200">
|
||||
Unerwartete Ausgabe von <code class="font-mono">fail2ban-client status</code>.
|
||||
<span class="opacity-80">Details in <code>storage/logs/laravel.log</code>.</span>
|
||||
</div>
|
||||
|
||||
@else
|
||||
<div class="space-y-2">
|
||||
@forelse($jails as $j)
|
||||
|
|
@ -32,19 +40,13 @@
|
|||
<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
|
||||
Bannzeit: {{ $j['bantime'] === -1 ? 'permanent' : ($j['bantime'].'s') }}
|
||||
</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>
|
||||
|
||||
{{-- 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
|
||||
|
|
@ -87,6 +89,76 @@
|
|||
{{-- @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="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>--}}
|
||||
|
||||
{{-- --}}{{-- 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>--}}
|
||||
{{-- </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>--}}
|
||||
{{-- @elseif($permDenied)--}}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,7 @@
|
|||
@push('modal.header')
|
||||
<div class="px-5 pt-5 pb-3 border-b border-white/10 backdrop-blur rounded-t-2xl">
|
||||
<h2 class="text-[18px] font-semibold text-slate-100">
|
||||
Fail2Ban – {{ $jail }}
|
||||
</h2>
|
||||
<p class="text-[13px] text-slate-300/80">
|
||||
Aktuell gebannte IPs und Restlaufzeiten.
|
||||
</p>
|
||||
<h2 class="text-[18px] font-semibold text-slate-100">Fail2Ban – {{ $jail }}</h2>
|
||||
<p class="text-[13px] text-slate-300/80">Aktuell gebannte IPs und Restlaufzeiten.</p>
|
||||
</div>
|
||||
@endpush
|
||||
|
||||
|
|
@ -14,13 +10,9 @@
|
|||
<div class="rounded-xl border px-4 py-3 {{ $r['box_class'] }}">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-white/90 font-mono text-[14px]">{{ $r['ip'] }}</div>
|
||||
<div class="text-[12px] text-white/80">
|
||||
{{ $r['time_text'] }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-1 text-[12px] text-white/55">
|
||||
{{ $r['meta_text'] }}
|
||||
<div class="text-[12px] text-white/80">{{ $r['time_text'] }}</div>
|
||||
</div>
|
||||
<div class="mt-1 text-[12px] text-white/55">{{ $r['meta_text'] }}</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white/70">
|
||||
|
|
@ -32,6 +24,7 @@
|
|||
@push('modal.footer')
|
||||
<div class="px-5 py-3 border-t border-white/10 backdrop-blur rounded-b-2xl">
|
||||
<div class="flex items-center gap-2 justify-end">
|
||||
{{-- WICHTIG: refresht NUR das Modal --}}
|
||||
<button wire:click="$dispatch('f2b:refresh-banlist')" 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>
|
||||
|
|
@ -45,3 +38,51 @@
|
|||
</div>
|
||||
</div>
|
||||
@endpush
|
||||
|
||||
{{--@push('modal.header')--}}
|
||||
{{-- <div class="px-5 pt-5 pb-3 border-b border-white/10 backdrop-blur rounded-t-2xl">--}}
|
||||
{{-- <h2 class="text-[18px] font-semibold text-slate-100">--}}
|
||||
{{-- Fail2Ban – {{ $jail }}--}}
|
||||
{{-- </h2>--}}
|
||||
{{-- <p class="text-[13px] text-slate-300/80">--}}
|
||||
{{-- Aktuell gebannte IPs und Restlaufzeiten.--}}
|
||||
{{-- </p>--}}
|
||||
{{-- </div>--}}
|
||||
{{--@endpush--}}
|
||||
|
||||
{{--<div class="p-5 space-y-3">--}}
|
||||
{{-- @forelse($rows as $r)--}}
|
||||
{{-- <div class="rounded-xl border px-4 py-3 {{ $r['box_class'] }}">--}}
|
||||
{{-- <div class="flex items-center justify-between">--}}
|
||||
{{-- <div class="text-white/90 font-mono text-[14px]">{{ $r['ip'] }}</div>--}}
|
||||
{{-- <div class="text-[12px] text-white/80">--}}
|
||||
{{-- {{ $r['time_text'] }}--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- <div class="mt-1 text-[12px] text-white/55">--}}
|
||||
{{-- {{ $r['meta_text'] }}--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @empty--}}
|
||||
{{-- <div class="rounded-xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white/70">--}}
|
||||
{{-- Keine gebannten IPs in diesem Jail.--}}
|
||||
{{-- </div>--}}
|
||||
{{-- @endforelse--}}
|
||||
{{--</div>--}}
|
||||
|
||||
{{--@push('modal.footer')--}}
|
||||
{{-- <div class="px-5 py-3 border-t border-white/10 backdrop-blur rounded-b-2xl">--}}
|
||||
{{-- <div class="flex items-center gap-2 justify-end">--}}
|
||||
{{-- <button wire:click="$dispatch('f2b:refresh-banlist')" 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>--}}
|
||||
{{-- <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">--}}
|
||||
{{-- Fertig--}}
|
||||
{{-- </button>--}}
|
||||
{{-- </div>--}}
|
||||
{{-- </div>--}}
|
||||
{{--@endpush--}}
|
||||
|
|
|
|||
Loading…
Reference in New Issue