user()?->can('manage-network'), 403); // 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 { abort_unless(auth()->user()?->can('manage-network'), 403); $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'); } }