121 lines
3.9 KiB
PHP
121 lines
3.9 KiB
PHP
<?php
|
|
|
|
namespace App\Livewire\Modals;
|
|
|
|
use App\Models\AuditEvent;
|
|
use App\Models\Server;
|
|
use App\Services\Fail2banService;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Str;
|
|
use Livewire\Attributes\Locked;
|
|
use LivewireUI\Modal\ModalComponent;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Scrollable list of fail2ban jails + their currently-banned IPs, lifted out of the
|
|
* servers/show panel so a long ban list never stretches the page. mount() only stores
|
|
* the serverId so the modal opens INSTANTLY; load() (fired by wire:init, behind a
|
|
* skeleton) does the live jail-status read via Fail2banService (the SAME service the
|
|
* page uses). unban() calls Fail2banService::unban (the SAME path as Show::unbanIp),
|
|
* audits the success, refreshes this modal's list, and dispatches `fail2banChanged`
|
|
* so the page panel re-reads too.
|
|
*/
|
|
class Fail2banBans extends ModalComponent
|
|
{
|
|
#[Locked]
|
|
public int $serverId;
|
|
|
|
/** Jail status rows (name/currentlyBanned/currentlyFailed/bannedIps) for the list. */
|
|
public array $jails = [];
|
|
|
|
/** True once the live status was read (distinguishes "no bans" from "not loaded"). */
|
|
public bool $loaded = false;
|
|
|
|
public ?string $error = null;
|
|
|
|
public function mount(int $serverId): void
|
|
{
|
|
// Store the id ONLY so the modal opens instantly; the SSH read happens in load().
|
|
$this->serverId = $serverId;
|
|
}
|
|
|
|
public static function modalMaxWidth(): string
|
|
{
|
|
return 'lg';
|
|
}
|
|
|
|
/** Lazy: the SSH read runs after the modal renders (wire:init), behind a skeleton. */
|
|
public function load(Fail2banService $fail2ban): void
|
|
{
|
|
$this->loadJails($fail2ban);
|
|
}
|
|
|
|
/** Read the live jail/banned-IP status (same Fail2banService::status the page calls). */
|
|
private function loadJails(Fail2banService $fail2ban): void
|
|
{
|
|
$server = Server::find($this->serverId);
|
|
if (! $server) {
|
|
$this->error = __('common.server_not_found');
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$status = $fail2ban->status($server);
|
|
$this->jails = $status['jails'] ?? [];
|
|
$this->error = ! empty($status['readError']) ? __('servers.fail2ban_read_error') : null;
|
|
$this->loaded = true;
|
|
} catch (Throwable $e) {
|
|
$this->error = Str::limit($e->getMessage(), 120);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Unban one IP — mirrors Show::unbanIp: call Fail2banService::unban, audit the
|
|
* success, refresh this list, and dispatch `fail2banChanged` so the page panel
|
|
* re-reads its own fail2ban state.
|
|
*/
|
|
public function unbanIp(string $jail, string $ip, Fail2banService $fail2ban): void
|
|
{
|
|
$server = Server::find($this->serverId);
|
|
if (! $server) {
|
|
$this->error = __('common.server_not_found');
|
|
|
|
return;
|
|
}
|
|
|
|
try {
|
|
$res = $fail2ban->unban($server, $jail, $ip);
|
|
} catch (Throwable $e) {
|
|
$this->dispatch('notify', message: __('servers.notify_unban_failed', ['error' => Str::limit($e->getMessage(), 90)]));
|
|
|
|
return;
|
|
}
|
|
if (! $res['ok']) {
|
|
$this->dispatch('notify', message: __('servers.notify_unban_failed', ['error' => Str::limit($res['output'] ?: __('servers.error_unknown'), 90)]));
|
|
|
|
return;
|
|
}
|
|
|
|
AuditEvent::create([
|
|
'user_id' => Auth::id(),
|
|
'server_id' => $server->id,
|
|
'actor' => Auth::user()?->name ?? 'system',
|
|
'action' => 'fail2ban.unban',
|
|
'target' => $ip.' · '.$jail.' · '.$server->name,
|
|
'ip' => request()->ip(),
|
|
]);
|
|
|
|
$this->dispatch('notify', message: __('servers.notify_ip_unbanned', ['ip' => $ip]));
|
|
|
|
// Re-read this modal's list, then tell the page panel to re-pull its own state.
|
|
$this->loadJails($fail2ban);
|
|
$this->dispatch('fail2banChanged');
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
return view('livewire.modals.fail2ban-bans');
|
|
}
|
|
}
|