mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-11 13:47:05 +02:00
add advanced ban actions
This commit is contained in:
826
README.md
826
README.md
@@ -1,132 +1,256 @@
|
|||||||
# **Fail2Ban UI**
|
# Fail2Ban UI
|
||||||
|
|
||||||
🚀 **Fail2Ban-UI** is a Swiss-made **web-based management interface** for [Fail2Ban](https://www.fail2ban.org/).
|
<div align="center">
|
||||||
It provides an intuitive dashboard to **monitor, configure, and manage Fail2Ban** instances in real time, supporting both local and remote Fail2ban servers.
|
|
||||||
|
|
||||||
Developed by **[Swissmakers GmbH](https://swissmakers.ch)**.
|
**Enterprise-Grade Intrusion Detection System Management Platform**
|
||||||
|
|
||||||
## **✨ Features**
|
[](https://www.gnu.org/licenses/gpl-3.0)
|
||||||
|
[](https://golang.org/)
|
||||||
|
[](https://www.linux.org/)
|
||||||
|
|
||||||
✅ **Multi-Server Management**
|
*Swiss-made open-source solution for centralized Fail2Ban management across distributed infrastructure*
|
||||||
- Manage **multiple Fail2ban servers** from a single interface
|
|
||||||
- Support for **local**, **SSH**, and **API agent** connections
|
|
||||||
- Add, edit, delete, and configure remote Fail2ban instances
|
|
||||||
- Switch between servers seamlessly via server selector
|
|
||||||
- Test connections before saving server configurations
|
|
||||||
|
|
||||||
✅ **Real-time Dashboard**
|
[Features](#-features) • [Quick Start](#-quick-start) • [Documentation](#-documentation) • [Screenshots](#-screenshots) • [Security Notes](#-security-notes)
|
||||||
- View **all active Fail2Ban jails** and **banned IPs** in a clean UI
|
|
||||||
- Displays **live ban events** from all configured servers
|
|
||||||
- **Internal log overview** with ban events stored in SQLite database
|
|
||||||
- Statistics per server and global overview
|
|
||||||
- Track ban events with country information and log lines
|
|
||||||
|
|
||||||
✅ **Ban & Unban Management**
|
</div>
|
||||||
- **Unban IPs** directly via the UI (works for local and remote servers)
|
|
||||||
- **Search** for banned IPs across all active jails
|
|
||||||
- View ban history with detailed information (IP, jail, hostname, country, logs)
|
|
||||||
|
|
||||||
✅ **Fail2Ban Configuration Management**
|
|
||||||
- **Edit & Save** active Fail2Ban jail/filter configs (local and remote)
|
|
||||||
- **Manage Jails**: Enable/disable jails on local and remote servers
|
|
||||||
- **Filter Debug**: Test filters against log lines using `fail2ban-regex`
|
|
||||||
- Get automatic **email alerts** for specific country-based bans
|
|
||||||
- Configure own SMTP settings for email alerts (STARTTLS only)
|
|
||||||
- Adjust default ban time, find time, and set ignore IPs
|
|
||||||
- Auto-detects changes and prompts for **reload** to apply
|
|
||||||
- Enable debug-mode for detailed module logs
|
|
||||||
|
|
||||||
✅ **SSH Remote Management**
|
|
||||||
- Connect to remote Fail2ban servers via SSH
|
|
||||||
- Automatic deployment of custom action for ban notifications
|
|
||||||
- Passwordless sudo support for seamless operation
|
|
||||||
- Configurable SSH key selection per server
|
|
||||||
- Test SSH connections before saving
|
|
||||||
|
|
||||||
✅ **API Agent Support** (Future)
|
|
||||||
- Connect to remote Fail2ban instances via lightweight API agents
|
|
||||||
- Secure communication using shared secrets
|
|
||||||
- Push ban notifications from remote servers to the UI
|
|
||||||
|
|
||||||
✅ **Persistent Storage**
|
|
||||||
- **SQLite database** for storing server configurations and ban events
|
|
||||||
- All settings and server configurations persist across restarts
|
|
||||||
- Automatic migration from legacy JSON-based settings
|
|
||||||
|
|
||||||
✅ **Mobile-Friendly & Responsive UI / Fast**
|
|
||||||
- Optimized for **mobile & desktop**
|
|
||||||
- Powered by **Tailwind CSS** (works offline when built locally)
|
|
||||||
- **Go-based backend** ensures minimal resource usage
|
|
||||||
- Parallel execution for improved performance on remote connections
|
|
||||||
|
|
||||||
✅ **Systemd & SELinux Support**
|
|
||||||
- **Run as a systemd service** (Standalone or Container)
|
|
||||||
- **Supports SELinux** for secure execution (also container version)
|
|
||||||
|
|
||||||
✅ **Internationalization**
|
|
||||||
- Multi-language support (English, German, French, Italian, Spanish)
|
|
||||||
- Easy to add new languages via JSON translation files
|
|
||||||
|
|
||||||
## **📸 Screenshots**
|
|
||||||
Some images from the UI in action:
|
|
||||||
|
|
||||||
| Dashboard | Server Management | Filter Debug |
|
|
||||||
|-----------|-------------------|--------------|
|
|
||||||
|  |  |  |
|
|
||||||
|
|
||||||
📌 **More screenshots are found [here](./screenshots/)**
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## **📥 Installation & Deployment**
|
## 🎯 Overview
|
||||||
|
|
||||||
Fail2Ban-UI can be currently deployed in **two main ways**:
|
**Fail2Ban UI** is a production-ready, enterprise-grade web-based management platform to create a distributed Fail2Ban intrusion detection system. Designed for organizations managing single or multible fail2ban instances. It provides centralized ban-control, real-time monitoring and alerting across all servers.
|
||||||
**1️⃣ Running from local source**
|
|
||||||
**2️⃣ Running as a container**
|
|
||||||
|
|
||||||
### **🔹 Method 1: Running from Local Source**
|
### Why we Need Fail2Ban UI
|
||||||
To install and run directly on the system:
|
|
||||||
📌 **[Follow the basic systemd setup guide](./deployment/systemd/README.md)**
|
|
||||||
|
|
||||||
```bash
|
Modern enterprises face increasing security challenges with generally distributed infrastructure, cloud deployments, and multi-location operations. Traditional Fail2Ban management requires a manual SSH access to individual servers, manual configuration changes, and lacks centralized visibility. **Fail2Ban UI solves these challenges** by providing:
|
||||||
git clone https://github.com/swissmakers/fail2ban-ui.git /opt/fail2ban-ui
|
|
||||||
cd /opt/fail2ban-ui
|
|
||||||
|
|
||||||
# Build Tailwind CSS for production (optional but recommended)
|
- **Centralized Management**: Control multible Fail2Ban instances from a single interface
|
||||||
# This requires Node.js and npm to be installed
|
- **Real-Time Visibility**: Monitor ban / security events across your entire proxy / webserver infrastructure (also planned to ingest all via connector to SIEM)
|
||||||
./build-tailwind.sh
|
- **Operational Efficiency**: Reduce administrative overhead with automated workflows e.g. auto-banning for recurring IPs, directly on the firewall.
|
||||||
|
|
||||||
# Build Go application
|
**Developed by [Swissmakers GmbH](https://swissmakers.ch)** — Trusted by enterprises swisswide for mission-critical security infrastructure.
|
||||||
go build -o fail2ban-ui ./cmd/server/main.go
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
**📌 Note on Tailwind CSS:**
|
|
||||||
- For **production/offline use**, build Tailwind CSS using `./build-tailwind.sh` before building the application
|
|
||||||
- This creates a local CSS file at `pkg/web/static/tailwind.css` that works offline
|
|
||||||
- The build script uses **Tailwind CSS v3** (latest v3.x, matches CDN version)
|
|
||||||
- If the local file is not found, the application will automatically fall back to the CDN (requires internet connection)
|
|
||||||
- The build script requires **Node.js** and **npm** to be installed
|
|
||||||
- The script works for both fresh installations and existing development environments
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### **🔹 Method 2: Running as a Container**
|
## 🚀 Current Features
|
||||||
For an easy containerized deployment:
|
|
||||||
📌 **[Follow the basic container deployment guide](./deployment/container/README.md)**
|
|
||||||
|
|
||||||
You can either build the image locally or pull the **official, automatically built image** from the Swissmakers registry.
|
### 🏢 Multi-Server Management
|
||||||
Every merge into the `main` branch triggers a new build that is published at:
|
|
||||||
|
**Centralized Control Across Distributed Fail2Ban Instances**
|
||||||
|
|
||||||
|
- **Unified Dashboard**: Monitor and manage multiple Fail2Ban servers from a single interface
|
||||||
|
- **Flexible Connectivity**: Support for local, SSH, and API agent connections form Fail2Ban-UI to other Fail2Ban Instances
|
||||||
|
- **Connection Testing**: Validate backend connectivity before activate them on the management UI
|
||||||
|
|
||||||
|
**Use Cases:**
|
||||||
|
- Multi-datacenter deployments with several reverse proxies
|
||||||
|
- Hybrid cloud environments
|
||||||
|
- Also integrate external Webservers
|
||||||
|
|
||||||
|
### 📊 Real-Time Security Intelligence
|
||||||
|
|
||||||
|
**Malicious Actor Visibility and Analytics**
|
||||||
|
|
||||||
|
- **Live Event Monitoring**: Real-time ban events from all configured / connected servers
|
||||||
|
- **Historical Analysis**: SQLite-based event storage with advanced querying
|
||||||
|
- **Geographic Intelligence**: Country-based threat analysis and visualization (Will be later send to SIEM - planned)
|
||||||
|
- **Recurring Threat Detection**: Identify persistent attackers across time windows
|
||||||
|
- **Ban Insights Dashboard**: Aggregated statistics
|
||||||
|
- **Event Correlation**: Track attack patterns across multiple servers and jails (planned with Elasticsearch)
|
||||||
|
|
||||||
|
**Business Value:**
|
||||||
|
- Faster threat response times
|
||||||
|
- Proactive security posture management
|
||||||
|
- Compliance reporting and audit trails
|
||||||
|
|
||||||
|
### 🛡️ Advanced Ban Management
|
||||||
|
|
||||||
|
**IP Blocking and Unblocking**
|
||||||
|
|
||||||
|
- **Cross-Jail Search**: Find banned IPs across all active jails instantly
|
||||||
|
- **Bulk Operations**: Manage multiple bans simultaneously
|
||||||
|
- **Ban History**: Complete audit trail with timestamps, ban-reasons, and context like whois information of the attacker
|
||||||
|
- **Whitelist Management**: Configure trusted IPs and networks
|
||||||
|
- **Automatic Unban**: Time-based ban expiration with configurable policies
|
||||||
|
- **Permanent Ban for Reccuring IPs**: Set a threshold in the Settings, after what amount of Bans a IP is automatically banned permanently on Firewall (Currently Supported Mikrotik/PFSense)
|
||||||
|
|
||||||
|
|
||||||
|
### ⚙️ Configuration Management
|
||||||
|
|
||||||
|
- **Remote Configuration**: Edit Fail2Ban jail and filter configurations remotely
|
||||||
|
- **Jail Management**: Enable/disable jails across multiple servers
|
||||||
|
- **Filter Testing**: Debug and validate filters using `fail2ban-regex` before enabeling
|
||||||
|
- **Template Management**: Standardize configurations across server groups (planned)
|
||||||
|
|
||||||
|
|
||||||
|
### 📧 Alerting
|
||||||
|
|
||||||
|
**Security Notifications**
|
||||||
|
|
||||||
|
- **Multi-Language Email Alerts**: Localized notifications in currently 5 supported languages
|
||||||
|
- **Country-Based Filtering**: Alert only on threats from specific geographic regions to reduce alert fatigue
|
||||||
|
- **SMTP Integration**: Support M365 Mail-Servers with STARTTLS
|
||||||
|
- **Emai Templates**: Currentls features a Modern and classic email design (more to come)
|
||||||
|
- **Alert Aggregation**: This feature is planned for the SIEM-modul
|
||||||
|
|
||||||
|
### 🔐 Enterprise Security
|
||||||
|
|
||||||
|
**Hardened for Production Environments**
|
||||||
|
|
||||||
|
- **SELinux Support**: Full compatibility with SELinux-enabled systems and pre-created custom policies
|
||||||
|
- **Container Security**: Secure containerized deployment with proper best-practisies
|
||||||
|
- **Least Privilege**: Only minimal permissions are used using FACLs and special sudo-rules
|
||||||
|
- **Audit Logging**: Comprehensive logging for compliance and forensics also in the future planned to ingest into a Elastic SIEM
|
||||||
|
- **Encrypted Communications Only**: Secure data transmission for all remote operations will be enforced
|
||||||
|
|
||||||
|
### 🌐 Internationalization
|
||||||
|
|
||||||
|
- **Multi-Language UI**: English, German (DE/CH), French, Italian, Spanish
|
||||||
|
- **Localized Content**: All user-facing content translated
|
||||||
|
- **RTL Support Ready**: Architecture supports right-to-left languages
|
||||||
|
- **Easy Extension**: Simple JSON-based translation system
|
||||||
|
|
||||||
|
### 📱 Modern User Experience
|
||||||
|
|
||||||
|
- **Responsive Design**: Full functionality also on mobile devices
|
||||||
|
- **Progressive Web App**: Works also in a no-internet / offline and restricted environment with local CSS/JS builds only
|
||||||
|
- **Fast Performance**: Go-based backend with minimal resource footprint
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📸 Screenshots
|
||||||
|
|
||||||
|
### Dashboard Overview
|
||||||
|
The central command center for monitoring all Fail2Ban instances and security events.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Server Management
|
||||||
|
Add, configure, and manage multiple Fail2Ban servers from the "Manage Servers" modal.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### IP Search and Management
|
||||||
|
Quickly locate and review / manage banned IPs across all jails and servers.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Unban Operations
|
||||||
|
One-click unban action with confirmation dialog.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Configuration Editor
|
||||||
|
Edit Fail2Ban jail and filter configurations (with syntax highlighting - planned) and validation.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Service Management
|
||||||
|
Reload or restart Fail2Ban services when needed, with integrated change detection.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Filter Debugging
|
||||||
|
Test and validate Fail2Ban filters using `fail2ban-regex`.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Settings and Configuration
|
||||||
|
Comprehensive settings management for alerts, advanced banning, and system preferences.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ Architecture
|
||||||
|
|
||||||
|
### System Components
|
||||||
|
|
||||||
```
|
```
|
||||||
registry.swissmakers.ch/infra/fail2ban-ui:latest
|
┌────────────────────────────────────────────────────────────┐
|
||||||
|
│ Fail2Ban UI Web Interface │
|
||||||
|
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
||||||
|
│ │ Dashboard │ │ Management │ │ Settings │ │
|
||||||
|
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
||||||
|
└────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌────────────────────────────────────────────────────────────────┐
|
||||||
|
│ Go Backend API Server │
|
||||||
|
│ ┌─────────────────────────────┐ ┌──────────────────────┐ │
|
||||||
|
│ │ Fail2Ban UI (Backend) │--->│ Send Alerts via Mail │ │
|
||||||
|
│ │ - Gin handlers + REST API │ │ (planned: Elastic) │ │
|
||||||
|
│ │ - Vanilla JS + Tailwind UI │ └──────────────────────┘ │
|
||||||
|
│ ->│ - SQLite storage │ │
|
||||||
|
│ │ └──────────────┬──────────────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ┌──────────┴────────────┐ ┌─────────────────────┐ │
|
||||||
|
│ │ │ Connector Manager and │-------│ Integrations │ │
|
||||||
|
│ │ │ handlers / actions │ │ Mikrotik / pfSense │ │
|
||||||
|
│ │ └────────────────────┬──┘ └─────────────────────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
└─│───────────────────────────│──────────────────────────────────┘
|
||||||
|
│ │
|
||||||
|
│ ▼
|
||||||
|
┌─│─────────────────────────────────────────────────────────────┐
|
||||||
|
│ │ Connection to remote Server │
|
||||||
|
│ │ ───────────────────────────── │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ ▼ ▼ ▼ │
|
||||||
|
│ │ ┌────────┐ ┌────────┐ ┌────────┐ │
|
||||||
|
│ │ │ Local │ │ SSH │ │ API │ │
|
||||||
|
│ │ │ Server │ │ Server │ │ Agent │ │
|
||||||
|
│ │ └────────┘ └────────┘ └────────┘ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ │ ┌─────┴─────────────┴─────────────┴─────┐ │
|
||||||
|
│ │ │ Fail2Ban instances on Reverse Proxies │ │
|
||||||
|
│ │ │ or remote / local Webserver │ │
|
||||||
|
│ │ └─────────────┬─────────────────────────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ┌──────────┴────────────┐ │
|
||||||
|
│ │ │ Report Alerts back to │ │
|
||||||
|
│ <----------│ Fail2Ban-UI REST with │ │
|
||||||
|
│ │ custom action │ │
|
||||||
|
│ └───────────────────────┘ │
|
||||||
|
└───────────────────────────────────────────────────────────────┘
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### Technology Stack
|
||||||
|
|
||||||
|
- **Backend**: Go 1.21+ (Golang)
|
||||||
|
- **Frontend**: Vanilla JavaScript, Tailwind CSS
|
||||||
|
- **Database**: SQLite (embedded)
|
||||||
|
- **Container Runtime**: Podman/Docker compatible
|
||||||
|
- **Service Management**: systemd
|
||||||
|
- **Security**: SELinux compatible
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 Quick Start
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- **Operating System**: Linux (RHEL 8+, Ubuntu 20.04+, Debian 11+, or containerized)
|
||||||
|
- **Fail2Ban**: At least version 0.10+ installed and configured
|
||||||
|
- **Go**: Version 1.21+ (only for source builds)
|
||||||
|
- **Node.js**: Version 16+ (for Tailwind CSS builds)
|
||||||
|
- **Permissions**: Root access to configure FACL and sudo-rules and Fail2Ban socket access
|
||||||
|
|
||||||
|
### Installation Methods
|
||||||
|
|
||||||
|
#### Method 1: Container Deployment (Recommended for Production)
|
||||||
|
|
||||||
|
**Pull the official image:**
|
||||||
```bash
|
```bash
|
||||||
# Pull the latest pre-built image
|
|
||||||
podman pull registry.swissmakers.ch/infra/fail2ban-ui:latest
|
podman pull registry.swissmakers.ch/infra/fail2ban-ui:latest
|
||||||
|
```
|
||||||
|
|
||||||
# Run the container
|
**Run the container:**
|
||||||
|
```bash
|
||||||
podman run -d \
|
podman run -d \
|
||||||
--name fail2ban-ui \
|
--name fail2ban-ui \
|
||||||
--network=host \
|
--network=host \
|
||||||
@@ -138,183 +262,421 @@ podman run -d \
|
|||||||
registry.swissmakers.ch/infra/fail2ban-ui:latest
|
registry.swissmakers.ch/infra/fail2ban-ui:latest
|
||||||
```
|
```
|
||||||
|
|
||||||
> **📌 Note:** The container can also be managed as a **systemd service**.
|
**📖 [Complete Container Deployment Guide](./deployment/container/README.md)**
|
||||||
|
|
||||||
|
#### Method 2: Systemd Service (Standalone)
|
||||||
|
|
||||||
|
**Clone and build:**
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/swissmakers/fail2ban-ui.git /opt/fail2ban-ui
|
||||||
|
cd /opt/fail2ban-ui
|
||||||
|
|
||||||
|
# Build Tailwind CSS (optional, for offline use)
|
||||||
|
./build-tailwind.sh
|
||||||
|
|
||||||
|
# Build Go application
|
||||||
|
go build -o fail2ban-ui ./cmd/server/main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
**📖 [Complete Systemd Setup Guide](./deployment/systemd/README.md)**
|
||||||
|
|
||||||
|
### First Launch
|
||||||
|
|
||||||
|
1. **Access the Web Interface**
|
||||||
|
- Navigate to `http://localhost:8080` (or your configured port)
|
||||||
|
- Default port: `8080` (configurable in settings)
|
||||||
|
|
||||||
|
2. **Add Your First Server**
|
||||||
|
- **Local Server**: Enable the local connector if Fail2Ban runs on the same host
|
||||||
|
- **Remote Server**: Add via SSH or API agent connection
|
||||||
|
|
||||||
|
3. **Configure Settings**
|
||||||
|
- Set up email alerts (optional)
|
||||||
|
- Configure language preferences
|
||||||
|
- Adjust security settings
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## **🚀 Getting Started**
|
## 📚 Documentation
|
||||||
|
|
||||||
### **First Launch**
|
### Deployment Guides
|
||||||
1. After starting Fail2Ban-UI, access the web interface at `http://localhost:8080` (or your configured port)
|
|
||||||
2. You'll be prompted to **add your first Fail2ban server** or **enable the local connector**
|
|
||||||
3. For local connections: Enable the local connector if Fail2ban is running on the same host
|
|
||||||
4. For remote connections: Add a new server via SSH or API agent
|
|
||||||
|
|
||||||
### **Adding a Server**
|
- **[Container Deployment](./deployment/container/README.md)**: Complete guide for containerized deployments
|
||||||
|
- **[Systemd Service Setup](./deployment/systemd/README.md)**: Standalone installation and service configuration
|
||||||
|
- **[SELinux Configuration](./deployment/container/SELinux/)**: Security policies for SELinux-enabled systems
|
||||||
|
|
||||||
#### **Local Server**
|
### Configuration
|
||||||
- The local connector is available by default but **disabled** initially
|
|
||||||
- Enable it in the server management settings if Fail2ban runs on the same host
|
|
||||||
- Requires root access or passwordless sudo to the Fail2ban socket
|
|
||||||
|
|
||||||
#### **SSH Server**
|
#### Adding a Local Server
|
||||||
1. Go to **Settings** → **Manage Servers**
|
|
||||||
2. Click **Add Server**
|
|
||||||
3. Select **SSH** as connection type
|
|
||||||
4. Fill in:
|
|
||||||
- **Name**: A descriptive name for this server
|
|
||||||
- **Host**: IP address or hostname
|
|
||||||
- **Port**: SSH port (default: 22)
|
|
||||||
- **SSH User**: Username for SSH connection
|
|
||||||
- **SSH Key**: Select an SSH key from your `~/.ssh/` directory (or `/config/.ssh/` when running in a container)
|
|
||||||
5. Click **Test Connection** to verify before saving
|
|
||||||
6. The UI will automatically deploy the custom action for ban notifications
|
|
||||||
|
|
||||||
**Requirements for SSH connections:**
|
The local connector allows managing Fail2Ban on the same host where Fail2Ban UI runs.
|
||||||
|
|
||||||
|
**Requirements:**
|
||||||
|
- Fail2Ban installed and running
|
||||||
|
- Special FACL Rules and passwordless sudo to `/usr/bin/fail2ban-client`
|
||||||
|
- Socket access: `/var/run/fail2ban/fail2ban.sock`
|
||||||
|
|
||||||
|
**Enable in UI:**
|
||||||
|
1. Navigate to **Settings** → **Manage Servers**
|
||||||
|
2. Enable **Local Connector**
|
||||||
|
3. Test connection to verify access
|
||||||
|
|
||||||
|
#### Adding an SSH Server
|
||||||
|
|
||||||
|
Connect to remote Fail2Ban instances via SSH for centralized management.
|
||||||
|
|
||||||
|
**Prerequisites:**
|
||||||
- SSH key-based authentication (passwordless login)
|
- SSH key-based authentication (passwordless login)
|
||||||
- Network connectivity from UI host to remote server
|
- Network connectivity from UI host to remote server
|
||||||
- The SSH user must have:
|
- Service account with appropriate permissions
|
||||||
- Sudo access to run `systemctl restart fail2ban` and `/usr/bin/fail2ban-client` (configured via sudoers)
|
|
||||||
- File system ACLs on `/etc/fail2ban` for read/write access to configuration files (no sudo needed for file operations)
|
**Recommended Service Account Setup:**
|
||||||
|
|
||||||
**Recommended SSH setup (using service account with ACLs):**
|
|
||||||
```bash
|
```bash
|
||||||
# Create service account
|
# Create dedicated service account
|
||||||
sudo useradd -r -s /bin/bash sa_fail2ban
|
sudo useradd -r -s /bin/bash sa_fail2ban
|
||||||
|
|
||||||
# Configure sudoers for fail2ban-client and systemctl restart
|
# Configure sudoers for Fail2Ban operations
|
||||||
sudo visudo -f /etc/sudoers.d/fail2ban-ui
|
sudo visudo -f /etc/sudoers.d/fail2ban-ui
|
||||||
# Add:
|
```
|
||||||
|
|
||||||
|
Add the following sudoers configuration:
|
||||||
|
```
|
||||||
sa_fail2ban ALL=(ALL) NOPASSWD: /usr/bin/fail2ban-client *
|
sa_fail2ban ALL=(ALL) NOPASSWD: /usr/bin/fail2ban-client *
|
||||||
sa_fail2ban ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart fail2ban
|
sa_fail2ban ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart fail2ban
|
||||||
|
```
|
||||||
|
|
||||||
# Set ACLs for /etc/fail2ban directory
|
**Set file system ACLs:**
|
||||||
|
```bash
|
||||||
|
# Grant read/write access to Fail2Ban configuration directory
|
||||||
sudo setfacl -Rm u:sa_fail2ban:rwX /etc/fail2ban
|
sudo setfacl -Rm u:sa_fail2ban:rwX /etc/fail2ban
|
||||||
sudo setfacl -dRm u:sa_fail2ban:rwX /etc/fail2ban
|
sudo setfacl -dRm u:sa_fail2ban:rwX /etc/fail2ban
|
||||||
```
|
```
|
||||||
|
|
||||||
This setup provides fine-grained permissions without requiring full passwordless sudo access.
|
**Add Server in UI:**
|
||||||
|
1. Navigate to **Settings** → **Manage Servers**
|
||||||
|
2. Click **Add Server**
|
||||||
|
3. Select **SSH** connection type
|
||||||
|
4. Configure:
|
||||||
|
- **Name**: Descriptive server identifier
|
||||||
|
- **Host**: IP address or hostname
|
||||||
|
- **Port**: SSH port (default: 22)
|
||||||
|
- **SSH User**: Service account username
|
||||||
|
- **SSH Key**: Select from `~/.ssh/` directory
|
||||||
|
5. Click **Test Connection** to verify
|
||||||
|
6. Save configuration
|
||||||
|
|
||||||
#### **API Agent Server** (Future)
|
**Security Benefits:**
|
||||||
- API agent support is planned for future releases
|
- Least privilege access model
|
||||||
- Will allow connecting to remote Fail2ban instances via a lightweight API service
|
- No full sudo access required
|
||||||
|
- Fine-grained file system permissions
|
||||||
|
- Audit trail via sudo logs
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## **🔒 Security Considerations**
|
### 🔒 Security Notes
|
||||||
|
|
||||||
- Fail2Ban-UI requires **root privileges** or **passwordless sudo** to interact with Fail2Ban sockets (for local connections).
|
#### Network Security Best Practices
|
||||||
- For SSH connections, use a **dedicated service account** with:
|
|
||||||
- **Limited sudo access** (only for `fail2ban-client` and `systemctl restart fail2ban`)
|
- **Reverse Proxy**: Use nginx or Apache as reverse proxy with SSL/TLS termination to secure the Fail2ban-UI
|
||||||
- **File system ACLs** on `/etc/fail2ban` for configuration file access (more secure than full sudo)
|
- **VPN Access**: Require VPN connection for access to Fail2Banu-UI only
|
||||||
- **Restrict access** using **firewall rules** or a **reverse proxy** with authentication.
|
- **IP Whitelisting**: Restrict access to specific IPs / ranges e.g. internal IT
|
||||||
- Ensure that Fail2Ban logs/configs **aren't exposed publicly**.
|
|
||||||
- For SSH connections, use **SSH key-based authentication** and restrict SSH access.
|
#### Authentication and Authorization
|
||||||
- Store SSH keys securely and use strong passphrases.
|
|
||||||
- The SQLite database contains sensitive information - ensure proper file permissions.
|
- **SSH Key Management**: Use strong SSH keys like 4096-bit RSA or even better Ed25519. When using RSA no smaler bit size please.
|
||||||
|
- **Service Accounts**: Use dedicated service accounts, not personal accounts
|
||||||
|
- **Sudoers Configuration**: Minimal sudo permissions, no passwordless full sudo
|
||||||
|
|
||||||
|
#### Data Protection
|
||||||
|
|
||||||
|
- **Database Permissions**: Restrict SQLite database file permissions (600)
|
||||||
|
- **Log Files**: Secure log file access and don't forget to setup a rotation
|
||||||
|
- **Backup Encryption**: It's always a good idea, to encrypt backups of configuration and database files
|
||||||
|
|
||||||
|
#### SELinux Configuration
|
||||||
|
|
||||||
|
For SELinux-enabled systems, apply the required policies:
|
||||||
|
|
||||||
For **SELinux users**, apply the **Fail2Ban-UI security policies**:
|
|
||||||
```bash
|
```bash
|
||||||
# Basic rule to allow fail2ban access the fail2ban-ui API
|
# Basic rule to allow Fail2Ban to access the UI API
|
||||||
semodule -i fail2ban-curl-allow.pp
|
semodule -i fail2ban-curl-allow.pp
|
||||||
# Also needed for a secure container deployment
|
|
||||||
|
# Container deployment policies
|
||||||
semodule -i fail2ban-container-ui.pp
|
semodule -i fail2ban-container-ui.pp
|
||||||
semodule -i fail2ban-container-client.pp
|
semodule -i fail2ban-container-client.pp
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
**📖 [SELinux Policies Documentation](./deployment/container/SELinux/)**
|
||||||
|
|
||||||
## **🛠️ Troubleshooting**
|
|
||||||
|
|
||||||
### **UI not accessible?**
|
|
||||||
- Ensure **port 8080** (or your configured port) is open:
|
|
||||||
```bash
|
|
||||||
sudo firewall-cmd --add-port=8080/tcp --permanent
|
|
||||||
sudo firewall-cmd --reload
|
|
||||||
```
|
|
||||||
- Check logs:
|
|
||||||
```bash
|
|
||||||
journalctl -u fail2ban-ui.service -f
|
|
||||||
```
|
|
||||||
|
|
||||||
### **No servers configured?**
|
|
||||||
- On first launch, you need to either:
|
|
||||||
- Enable the local connector (if Fail2ban runs locally)
|
|
||||||
- Add a remote server via SSH or API agent
|
|
||||||
- Go to **Settings** → **Manage Servers** to configure your first server
|
|
||||||
|
|
||||||
### **SSH connection issues?**
|
|
||||||
- Verify SSH key authentication works manually:
|
|
||||||
```bash
|
|
||||||
ssh -i ~/.ssh/your_key user@remote-host
|
|
||||||
```
|
|
||||||
- Ensure the SSH user has proper permissions:
|
|
||||||
- Check sudo access for `fail2ban-client` and `systemctl restart fail2ban`:
|
|
||||||
```bash
|
|
||||||
sudo -l -U sa_fail2ban
|
|
||||||
```
|
|
||||||
- Verify ACLs on `/etc/fail2ban`:
|
|
||||||
```bash
|
|
||||||
getfacl /etc/fail2ban
|
|
||||||
```
|
|
||||||
- Check debug mode in settings for detailed error messages
|
|
||||||
- Verify the SSH user has access to `/var/run/fail2ban/fail2ban.sock`
|
|
||||||
|
|
||||||
### **Local connector not working?**
|
|
||||||
- Ensure Fail2ban is running: `sudo systemctl status fail2ban`
|
|
||||||
- Check socket permissions: `ls -la /var/run/fail2ban/fail2ban.sock`
|
|
||||||
- Verify the UI has access (runs as root or has sudo permissions)
|
|
||||||
|
|
||||||
### **Database issues?**
|
|
||||||
- The SQLite database is stored in the application directory
|
|
||||||
- Check file permissions if you see database errors
|
|
||||||
- Backup the database before major updates
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## **📊 Database**
|
### 🗄️ Database Notes
|
||||||
|
|
||||||
Fail2Ban-UI uses an embedded **SQLite database** (`fail2ban-ui.db`) to store:
|
#### SQLite Database Schema
|
||||||
- **Server configurations**: All configured Fail2ban servers (local, SSH, API agent)
|
|
||||||
- **Application settings**: UI preferences, SMTP settings, etc.
|
|
||||||
- **Ban events**: Historical ban records with IP, jail, hostname, country, and log lines
|
|
||||||
|
|
||||||
The database is automatically created on first launch and migrated from legacy JSON settings if present.
|
Fail2Ban UI uses an embedded SQLite database (`fail2ban-ui.db`) for persistent storage:
|
||||||
|
|
||||||
|
**Tables:**
|
||||||
|
- **`servers`**: Server configurations (local, SSH, API agent)
|
||||||
|
- **`app_settings`**: Application preferences and settings of Fail2Ban-UI
|
||||||
|
- **`ban_events`**: Historical ban records with full context
|
||||||
|
- **`permanent_blocks`**: Permanent block records for integrations
|
||||||
|
|
||||||
|
**Data Retention:**
|
||||||
|
- Ban events are stored indefinitely (configurable)
|
||||||
|
- Automatic database migrations on version updates
|
||||||
|
- Backup recommended before major updates
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## **🌐 Internationalization**
|
### 🌍 Internationalization Notes
|
||||||
|
|
||||||
Fail2Ban-UI supports multiple languages:
|
#### Currently Supported Languages
|
||||||
- English (en)
|
|
||||||
- German (de, de_ch)
|
|
||||||
- French (fr)
|
|
||||||
- Italian (it)
|
|
||||||
- Spanish (es)
|
|
||||||
|
|
||||||
Change the language in **Settings** → **Language**. New languages can be added by creating translation files in `internal/locales/`.
|
- **English** (en) - Default
|
||||||
|
- **German** (de, de_CH) - Standard and Swiss variants
|
||||||
|
- **French** (fr)
|
||||||
|
- **Italian** (it)
|
||||||
|
- **Spanish** (es)
|
||||||
|
|
||||||
|
#### Adding New Languages
|
||||||
|
|
||||||
|
1. Create translation file: `internal/locales/{language_code}.json`
|
||||||
|
2. Copy structure from `internal/locales/en.json`
|
||||||
|
3. Translate all key-value pairs
|
||||||
|
4. Test in UI: **Settings** → **Language** → Select new language
|
||||||
|
|
||||||
|
**Translation File Structure:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"page.title": "Fail2ban UI Dashboard",
|
||||||
|
"nav.dashboard": "Dashboard",
|
||||||
|
"nav.settings": "Settings",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## **🤝 Contributing**
|
### 📊 API Reference
|
||||||
We welcome **pull requests** and **feature suggestions**!
|
|
||||||
|
|
||||||
1. **Fork** this repository
|
Fail2Ban UI provides a RESTful API for programmatic access:
|
||||||
2. **Create** a new branch:
|
|
||||||
|
#### Endpoints
|
||||||
|
|
||||||
|
**Server Management:**
|
||||||
|
- `GET /api/servers` - List all configured servers
|
||||||
|
- `POST /api/servers` - Add or update server
|
||||||
|
- `DELETE /api/servers/:id` - Remove server
|
||||||
|
- `POST /api/servers/:id/test` - Test server connection
|
||||||
|
|
||||||
|
**Jail Management:**
|
||||||
|
- `GET /api/summary` - Get summary of all jails
|
||||||
|
- `POST /api/jails/:jail/unban/:ip` - Unban IP address
|
||||||
|
- `GET /api/jails/manage` - List jail management status
|
||||||
|
- `POST /api/jails/manage` - Update jail enabled states
|
||||||
|
|
||||||
|
**Configuration:**
|
||||||
|
- `GET /api/jails/:jail/config` - Get jail/filter configuration
|
||||||
|
- `POST /api/jails/:jail/config` - Update jail/filter configuration
|
||||||
|
|
||||||
|
**Events and Analytics:**
|
||||||
|
- `GET /api/events/bans` - List ban events
|
||||||
|
- `GET /api/events/bans/stats` - Get ban statistics
|
||||||
|
- `GET /api/events/bans/insights` - Get ban insights and analytics
|
||||||
|
|
||||||
|
**Settings:**
|
||||||
|
- `GET /api/settings` - Get application settings
|
||||||
|
- `POST /api/settings` - Update application settings
|
||||||
|
- `POST /api/settings/test-email` - Test email configuration
|
||||||
|
|
||||||
|
**Filter Debugging:**
|
||||||
|
- `GET /api/filters` - List available filters
|
||||||
|
- `POST /api/filters/test` - Test filter against log lines
|
||||||
|
|
||||||
|
**Service Control:**
|
||||||
|
- `POST /api/fail2ban/restart` - Restart Fail2Ban service
|
||||||
|
|
||||||
|
**Notifications:**
|
||||||
|
- `POST /api/ban` - Receive ban notification from Fail2Ban
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🛠️ Troubleshooting
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
#### UI Not Accessible
|
||||||
|
|
||||||
|
**Symptoms:** Cannot access web interface
|
||||||
|
|
||||||
|
**Solution / Check:**
|
||||||
|
```bash
|
||||||
|
# Check if service is running
|
||||||
|
systemctl status fail2ban-ui
|
||||||
|
|
||||||
|
# Check firewall rules
|
||||||
|
sudo firewall-cmd --list-ports
|
||||||
|
sudo firewall-cmd --add-port=8080/tcp --permanent
|
||||||
|
sudo firewall-cmd --reload
|
||||||
|
|
||||||
|
# Check logs
|
||||||
|
journalctl -u fail2ban-ui.service -f
|
||||||
|
```
|
||||||
|
|
||||||
|
#### No Servers Configured
|
||||||
|
|
||||||
|
**Symptoms:** Empty dashboard, no servers visible
|
||||||
|
|
||||||
|
**Solution / Check:**
|
||||||
|
1. Navigate to **Settings** → **Manage Servers**
|
||||||
|
2. Enable **Local Connector** (if Fail2Ban runs locally)
|
||||||
|
3. Add remote server via SSH or API agent
|
||||||
|
4. Verify server connection status
|
||||||
|
|
||||||
|
#### SSH Connection Issues
|
||||||
|
|
||||||
|
**Symptoms:** Cannot connect to remote server
|
||||||
|
|
||||||
|
**Solution / Check:**
|
||||||
|
```bash
|
||||||
|
# Test SSH connection manually
|
||||||
|
ssh -i ~/.ssh/your_key user@remote-host
|
||||||
|
|
||||||
|
# Verify SSH user permissions
|
||||||
|
sudo -l -U sa_fail2ban
|
||||||
|
|
||||||
|
# Check ACLs on /etc/fail2ban
|
||||||
|
getfacl /etc/fail2ban
|
||||||
|
|
||||||
|
# Enable debug mode in UI settings for detailed error messages
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Local Connector Not Working
|
||||||
|
|
||||||
|
**Symptoms:** Local server shows as disconnected
|
||||||
|
|
||||||
|
**Solution / Check:**
|
||||||
|
```bash
|
||||||
|
# Verify Fail2Ban is running
|
||||||
|
sudo systemctl status fail2ban
|
||||||
|
|
||||||
|
# Check socket permissions
|
||||||
|
ls -la /var/run/fail2ban/fail2ban.sock
|
||||||
|
|
||||||
|
# Verify UI has access (runs as root or has sudo permissions)
|
||||||
|
sudo fail2ban-client status
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Database Errors
|
||||||
|
|
||||||
|
**Symptoms:** Database-related errors in logs
|
||||||
|
|
||||||
|
**Solution / Check:**
|
||||||
|
```bash
|
||||||
|
# Check database file permissions
|
||||||
|
ls -la /opt/fail2ban-ui/fail2ban-ui.db
|
||||||
|
|
||||||
|
# Verify database integrity
|
||||||
|
sqlite3 /opt/fail2ban-ui/fail2ban-ui.db "PRAGMA integrity_check;"
|
||||||
|
|
||||||
|
# Backup and recreate if corrupted
|
||||||
|
cp fail2ban-ui.db fail2ban-ui.db.backup
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🤝 Contributing
|
||||||
|
|
||||||
|
We welcome contributions from the community! Whether it's bug fixes, feature enhancements, or documentation improvements, your contributions help make Fail2Ban UI better for everyone.
|
||||||
|
|
||||||
|
### How to Contribute
|
||||||
|
|
||||||
|
1. **Fork the Repository**
|
||||||
```bash
|
```bash
|
||||||
git checkout -b feature/my-feature
|
git clone https://github.com/swissmakers/fail2ban-ui.git
|
||||||
|
cd fail2ban-ui
|
||||||
```
|
```
|
||||||
3. **Commit** your changes:
|
|
||||||
```bash
|
|
||||||
git commit -m "Add new feature"
|
|
||||||
```
|
|
||||||
4. **Push** to the branch:
|
|
||||||
```bash
|
|
||||||
git push origin feature/my-feature
|
|
||||||
```
|
|
||||||
5. **Open** a Pull Request
|
|
||||||
|
|
||||||
|
2. **Create a Feature Branch**
|
||||||
|
```bash
|
||||||
|
git checkout -b feature/your-feature-name
|
||||||
|
```
|
||||||
|
|
||||||
## **📜 License**
|
3. **Make Your Changes**
|
||||||
Fail2Ban-UI is licensed under **GNU GENERAL PUBLIC LICENSE, Version 3**.
|
- Follow Go coding standards
|
||||||
See [`LICENSE`](./LICENSE) for details.
|
- Add tests for new features
|
||||||
|
- Update documentation as needed
|
||||||
|
|
||||||
|
4. **Commit Your Changes**
|
||||||
|
```bash
|
||||||
|
git commit -m "Add: Description of your feature"
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Push and Create Pull Request**
|
||||||
|
```bash
|
||||||
|
git push origin feature/your-feature-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### Contribution Guidelines
|
||||||
|
|
||||||
|
- **Code Style**: Follow Go standard formatting (`gofmt`)
|
||||||
|
- **Testing**: Test your changes thoroughly
|
||||||
|
- **Documentation**: Update README and inline documentation
|
||||||
|
- **Commit Messages**: Use clear, descriptive commit messages
|
||||||
|
- **Pull Requests**: Provide detailed description of changes
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📜 License
|
||||||
|
|
||||||
|
Fail2Ban UI is licensed under the **GNU General Public License v3.0 (GPL-3.0)**.
|
||||||
|
|
||||||
|
This means:
|
||||||
|
- ✅ **Free to use** in commercial and non-commercial projects
|
||||||
|
- ✅ **Free to modify** and distribute
|
||||||
|
- ✅ **Source code available** for inspection and auditing
|
||||||
|
- ⚠️ **Copyleft**: Modifications must be released under the same license
|
||||||
|
|
||||||
|
**Full License Text:** [LICENSE](./LICENSE)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏢 Enterprise Support
|
||||||
|
|
||||||
|
### Professional Services
|
||||||
|
|
||||||
|
**Swissmakers GmbH** offers professional services for Fail2Ban UI:
|
||||||
|
|
||||||
|
- **Enterprise Deployment**: Custom deployment and configuration
|
||||||
|
- **Training and Support**: On-site or remote training sessions
|
||||||
|
- **Custom Development**: Feature development and integrations
|
||||||
|
- **Security Audits**: Security assessment and hardening
|
||||||
|
|
||||||
|
**Contact:** [https://swissmakers.ch](https://swissmakers.ch)
|
||||||
|
|
||||||
|
### Community Support
|
||||||
|
|
||||||
|
- **GitHub Issues**: [Report bugs and request features](https://github.com/swissmakers/fail2ban-ui/issues)
|
||||||
|
- **Documentation**: Comprehensive guides and API reference
|
||||||
|
- **Community**: Join discussions and share experiences
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🙏 Acknowledgments
|
||||||
|
|
||||||
|
Fail2Ban UI is built on the foundation of the excellent [Fail2Ban](https://www.fail2ban.org/) project and the open-source community.
|
||||||
|
|
||||||
|
**Special Thanks:**
|
||||||
|
- Fail2Ban developers and contributors
|
||||||
|
- Go community and ecosystem
|
||||||
|
- All contributors and users of Fail2Ban UI
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
**Fail2Ban UI** — *Enterprise-Grade Intrusion Detection System Management*
|
||||||
|
|
||||||
|
</div>
|
||||||
2
go.mod
2
go.mod
@@ -6,6 +6,7 @@ require (
|
|||||||
github.com/gin-gonic/gin v1.10.0
|
github.com/gin-gonic/gin v1.10.0
|
||||||
github.com/go-playground/validator/v10 v10.26.0
|
github.com/go-playground/validator/v10 v10.26.0
|
||||||
github.com/oschwald/maxminddb-golang v1.13.1
|
github.com/oschwald/maxminddb-golang v1.13.1
|
||||||
|
golang.org/x/crypto v0.33.0
|
||||||
modernc.org/sqlite v1.33.1
|
modernc.org/sqlite v1.33.1
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -33,7 +34,6 @@ require (
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
golang.org/x/arch v0.13.0 // indirect
|
golang.org/x/arch v0.13.0 // indirect
|
||||||
golang.org/x/crypto v0.33.0 // indirect
|
|
||||||
golang.org/x/net v0.34.0 // indirect
|
golang.org/x/net v0.34.0 // indirect
|
||||||
golang.org/x/sys v0.30.0 // indirect
|
golang.org/x/sys v0.30.0 // indirect
|
||||||
golang.org/x/text v0.22.0 // indirect
|
golang.org/x/text v0.22.0 // indirect
|
||||||
|
|||||||
2
go.sum
2
go.sum
@@ -90,6 +90,8 @@ golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
|||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc=
|
||||||
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU=
|
||||||
|
golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s=
|
||||||
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM=
|
||||||
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY=
|
||||||
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg=
|
||||||
|
|||||||
@@ -126,7 +126,7 @@
|
|||||||
"settings.advanced.title": "Erweiterte Aktionen für Wiederholungstäter",
|
"settings.advanced.title": "Erweiterte Aktionen für Wiederholungstäter",
|
||||||
"settings.advanced.description": "Synchronisiere wiederholte Angreifer automatisch mit einer externen Firewall oder Blockliste.",
|
"settings.advanced.description": "Synchronisiere wiederholte Angreifer automatisch mit einer externen Firewall oder Blockliste.",
|
||||||
"settings.advanced.refresh_log": "Protokoll aktualisieren",
|
"settings.advanced.refresh_log": "Protokoll aktualisieren",
|
||||||
"settings.advanced.test_button": "Integration testen",
|
"settings.advanced.test_button": "Manuell sperren / Test",
|
||||||
"settings.advanced.enable": "Automatische permanente Sperre aktivieren",
|
"settings.advanced.enable": "Automatische permanente Sperre aktivieren",
|
||||||
"settings.advanced.threshold": "Schwelle vor permanenter Sperre",
|
"settings.advanced.threshold": "Schwelle vor permanenter Sperre",
|
||||||
"settings.advanced.threshold_hint": "Sobald eine IP diesen Wert erreicht, wird sie an die Integration übergeben.",
|
"settings.advanced.threshold_hint": "Sobald eine IP diesen Wert erreicht, wird sie an die Integration übergeben.",
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
"settings.advanced.log_updated": "Aktualisiert",
|
"settings.advanced.log_updated": "Aktualisiert",
|
||||||
"settings.advanced.log_actions": "Aktionen",
|
"settings.advanced.log_actions": "Aktionen",
|
||||||
"settings.advanced.unblock_btn": "Entfernen",
|
"settings.advanced.unblock_btn": "Entfernen",
|
||||||
"settings.advanced.test_title": "Integration testen",
|
"settings.advanced.test_title": "Manuell sperren / Test",
|
||||||
"settings.advanced.test_ip": "IP-Adresse",
|
"settings.advanced.test_ip": "IP-Adresse",
|
||||||
"settings.advanced.test_server": "Optionaler Server",
|
"settings.advanced.test_server": "Optionaler Server",
|
||||||
"settings.advanced.test_server_none": "Globale Integration verwenden",
|
"settings.advanced.test_server_none": "Globale Integration verwenden",
|
||||||
|
|||||||
@@ -126,7 +126,7 @@
|
|||||||
"settings.advanced.title": "Erwieterti Aktione für Wiederholungstäter",
|
"settings.advanced.title": "Erwieterti Aktione für Wiederholungstäter",
|
||||||
"settings.advanced.description": "Synchronisier d wiederholigstäter automatisch mit dr externe Firewall resp. Sperrlischte.",
|
"settings.advanced.description": "Synchronisier d wiederholigstäter automatisch mit dr externe Firewall resp. Sperrlischte.",
|
||||||
"settings.advanced.refresh_log": "Log aktualisiere",
|
"settings.advanced.refresh_log": "Log aktualisiere",
|
||||||
"settings.advanced.test_button": "Integration teste",
|
"settings.advanced.test_button": "Manuell sperre / Test",
|
||||||
"settings.advanced.enable": "Automatischi permanenti Sperrig aktiviere",
|
"settings.advanced.enable": "Automatischi permanenti Sperrig aktiviere",
|
||||||
"settings.advanced.threshold": "Schweue für die permanenti Sperrig",
|
"settings.advanced.threshold": "Schweue für die permanenti Sperrig",
|
||||||
"settings.advanced.threshold_hint": "Sobald e IP die Zahu erreitcht, wird sie via Integration gsperrt.",
|
"settings.advanced.threshold_hint": "Sobald e IP die Zahu erreitcht, wird sie via Integration gsperrt.",
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
"settings.advanced.log_updated": "Aktualisiert",
|
"settings.advanced.log_updated": "Aktualisiert",
|
||||||
"settings.advanced.log_actions": "Aktione",
|
"settings.advanced.log_actions": "Aktione",
|
||||||
"settings.advanced.unblock_btn": "Entferne",
|
"settings.advanced.unblock_btn": "Entferne",
|
||||||
"settings.advanced.test_title": "Integration teste",
|
"settings.advanced.test_title": "Manuell sperre / Test",
|
||||||
"settings.advanced.test_ip": "IP-Adrässe",
|
"settings.advanced.test_ip": "IP-Adrässe",
|
||||||
"settings.advanced.test_server": "Optionale Server",
|
"settings.advanced.test_server": "Optionale Server",
|
||||||
"settings.advanced.test_server_none": "Globali Integration bruuchä",
|
"settings.advanced.test_server_none": "Globali Integration bruuchä",
|
||||||
|
|||||||
@@ -126,7 +126,7 @@
|
|||||||
"settings.advanced.title": "Advanced Actions for Recurring Offenders",
|
"settings.advanced.title": "Advanced Actions for Recurring Offenders",
|
||||||
"settings.advanced.description": "Automatically synchronize recurring offenders to an external firewall or blocklist.",
|
"settings.advanced.description": "Automatically synchronize recurring offenders to an external firewall or blocklist.",
|
||||||
"settings.advanced.refresh_log": "Refresh Log",
|
"settings.advanced.refresh_log": "Refresh Log",
|
||||||
"settings.advanced.test_button": "Test Integration",
|
"settings.advanced.test_button": "Manually Block / Test",
|
||||||
"settings.advanced.enable": "Enable automatic permanent blocking",
|
"settings.advanced.enable": "Enable automatic permanent blocking",
|
||||||
"settings.advanced.threshold": "Threshold before permanent block",
|
"settings.advanced.threshold": "Threshold before permanent block",
|
||||||
"settings.advanced.threshold_hint": "Once an IP reaches this number of bans it will be forwarded to the integration.",
|
"settings.advanced.threshold_hint": "Once an IP reaches this number of bans it will be forwarded to the integration.",
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
"settings.advanced.log_updated": "Updated",
|
"settings.advanced.log_updated": "Updated",
|
||||||
"settings.advanced.log_actions": "Actions",
|
"settings.advanced.log_actions": "Actions",
|
||||||
"settings.advanced.unblock_btn": "Remove",
|
"settings.advanced.unblock_btn": "Remove",
|
||||||
"settings.advanced.test_title": "Test Advanced Integration",
|
"settings.advanced.test_title": "Manually Block / Test",
|
||||||
"settings.advanced.test_ip": "IP address",
|
"settings.advanced.test_ip": "IP address",
|
||||||
"settings.advanced.test_server": "Optional server",
|
"settings.advanced.test_server": "Optional server",
|
||||||
"settings.advanced.test_server_none": "Use global integration settings",
|
"settings.advanced.test_server_none": "Use global integration settings",
|
||||||
|
|||||||
@@ -126,7 +126,7 @@
|
|||||||
"settings.advanced.title": "Acciones avanzadas para reincidentes",
|
"settings.advanced.title": "Acciones avanzadas para reincidentes",
|
||||||
"settings.advanced.description": "Añade automáticamente IPs reincidentes a un firewall o lista de bloqueo externa.",
|
"settings.advanced.description": "Añade automáticamente IPs reincidentes a un firewall o lista de bloqueo externa.",
|
||||||
"settings.advanced.refresh_log": "Actualizar registro",
|
"settings.advanced.refresh_log": "Actualizar registro",
|
||||||
"settings.advanced.test_button": "Probar integración",
|
"settings.advanced.test_button": "Bloquear manualmente / Test",
|
||||||
"settings.advanced.enable": "Habilitar bloqueo permanente automático",
|
"settings.advanced.enable": "Habilitar bloqueo permanente automático",
|
||||||
"settings.advanced.threshold": "Umbral antes del bloqueo permanente",
|
"settings.advanced.threshold": "Umbral antes del bloqueo permanente",
|
||||||
"settings.advanced.threshold_hint": "Cuando una IP alcanza este número de bloqueos se enviará a la integración.",
|
"settings.advanced.threshold_hint": "Cuando una IP alcanza este número de bloqueos se enviará a la integración.",
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
"settings.advanced.log_updated": "Actualizado",
|
"settings.advanced.log_updated": "Actualizado",
|
||||||
"settings.advanced.log_actions": "Acciones",
|
"settings.advanced.log_actions": "Acciones",
|
||||||
"settings.advanced.unblock_btn": "Eliminar",
|
"settings.advanced.unblock_btn": "Eliminar",
|
||||||
"settings.advanced.test_title": "Probar integración avanzada",
|
"settings.advanced.test_title": "Bloquear manualmente / Test",
|
||||||
"settings.advanced.test_ip": "Dirección IP",
|
"settings.advanced.test_ip": "Dirección IP",
|
||||||
"settings.advanced.test_server": "Servidor opcional",
|
"settings.advanced.test_server": "Servidor opcional",
|
||||||
"settings.advanced.test_server_none": "Usar integración global",
|
"settings.advanced.test_server_none": "Usar integración global",
|
||||||
|
|||||||
@@ -126,7 +126,7 @@
|
|||||||
"settings.advanced.title": "Actions avancées pour récidivistes",
|
"settings.advanced.title": "Actions avancées pour récidivistes",
|
||||||
"settings.advanced.description": "Ajoutez automatiquement les récidivistes à un pare-feu ou une liste de blocage externe.",
|
"settings.advanced.description": "Ajoutez automatiquement les récidivistes à un pare-feu ou une liste de blocage externe.",
|
||||||
"settings.advanced.refresh_log": "Actualiser le journal",
|
"settings.advanced.refresh_log": "Actualiser le journal",
|
||||||
"settings.advanced.test_button": "Tester l’intégration",
|
"settings.advanced.test_button": "Bloquer manuellement / Test",
|
||||||
"settings.advanced.enable": "Activer le blocage permanent automatique",
|
"settings.advanced.enable": "Activer le blocage permanent automatique",
|
||||||
"settings.advanced.threshold": "Seuil avant blocage permanent",
|
"settings.advanced.threshold": "Seuil avant blocage permanent",
|
||||||
"settings.advanced.threshold_hint": "Une IP atteignant ce nombre de bans sera envoyée à l’intégration.",
|
"settings.advanced.threshold_hint": "Une IP atteignant ce nombre de bans sera envoyée à l’intégration.",
|
||||||
@@ -156,7 +156,7 @@
|
|||||||
"settings.advanced.log_updated": "Mis à jour",
|
"settings.advanced.log_updated": "Mis à jour",
|
||||||
"settings.advanced.log_actions": "Actions",
|
"settings.advanced.log_actions": "Actions",
|
||||||
"settings.advanced.unblock_btn": "Retirer",
|
"settings.advanced.unblock_btn": "Retirer",
|
||||||
"settings.advanced.test_title": "Tester l’intégration avancée",
|
"settings.advanced.test_title": "Bloquer manuellement / Test",
|
||||||
"settings.advanced.test_ip": "Adresse IP",
|
"settings.advanced.test_ip": "Adresse IP",
|
||||||
"settings.advanced.test_server": "Serveur optionnel",
|
"settings.advanced.test_server": "Serveur optionnel",
|
||||||
"settings.advanced.test_server_none": "Utiliser l’intégration globale",
|
"settings.advanced.test_server_none": "Utiliser l’intégration globale",
|
||||||
|
|||||||
@@ -40,12 +40,12 @@ func evaluateAdvancedActions(ctx context.Context, settings config.AppSettings, s
|
|||||||
"reason": "automatic_threshold",
|
"reason": "automatic_threshold",
|
||||||
"count": count,
|
"count": count,
|
||||||
"threshold": cfg.Threshold,
|
"threshold": cfg.Threshold,
|
||||||
}); err != nil {
|
}, false); err != nil {
|
||||||
log.Printf("⚠️ Failed to permanently block %s: %v", ip, err)
|
log.Printf("⚠️ Failed to permanently block %s: %v", ip, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func runAdvancedIntegrationAction(ctx context.Context, action, ip string, settings config.AppSettings, server config.Fail2banServer, details map[string]any) error {
|
func runAdvancedIntegrationAction(ctx context.Context, action, ip string, settings config.AppSettings, server config.Fail2banServer, details map[string]any, skipLoggingIfAlreadyBlocked bool) error {
|
||||||
cfg := settings.AdvancedActions
|
cfg := settings.AdvancedActions
|
||||||
if cfg.Integration == "" {
|
if cfg.Integration == "" {
|
||||||
return fmt.Errorf("no integration configured")
|
return fmt.Errorf("no integration configured")
|
||||||
@@ -85,26 +85,29 @@ func runAdvancedIntegrationAction(ctx context.Context, action, ip string, settin
|
|||||||
}[action]
|
}[action]
|
||||||
|
|
||||||
message := fmt.Sprintf("%s via %s", strings.Title(action), cfg.Integration)
|
message := fmt.Sprintf("%s via %s", strings.Title(action), cfg.Integration)
|
||||||
if err != nil {
|
if err != nil && !skipLoggingIfAlreadyBlocked {
|
||||||
status = "error"
|
status = "error"
|
||||||
message = err.Error()
|
message = err.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
if details == nil {
|
// If IP is already blocked, don't update the database entry - leave existing entry as is
|
||||||
details = map[string]any{}
|
if !skipLoggingIfAlreadyBlocked {
|
||||||
}
|
if details == nil {
|
||||||
details["action"] = action
|
details = map[string]any{}
|
||||||
detailsBytes, _ := json.Marshal(details)
|
}
|
||||||
rec := storage.PermanentBlockRecord{
|
details["action"] = action
|
||||||
IP: ip,
|
detailsBytes, _ := json.Marshal(details)
|
||||||
Integration: cfg.Integration,
|
rec := storage.PermanentBlockRecord{
|
||||||
Status: status,
|
IP: ip,
|
||||||
Message: message,
|
Integration: cfg.Integration,
|
||||||
ServerID: server.ID,
|
Status: status,
|
||||||
Details: string(detailsBytes),
|
Message: message,
|
||||||
}
|
ServerID: server.ID,
|
||||||
if err2 := storage.UpsertPermanentBlock(ctx, rec); err2 != nil {
|
Details: string(detailsBytes),
|
||||||
log.Printf("⚠️ Failed to record permanent block entry: %v", err2)
|
}
|
||||||
|
if err2 := storage.UpsertPermanentBlock(ctx, rec); err2 != nil {
|
||||||
|
log.Printf("⚠️ Failed to record permanent block entry: %v", err2)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
|||||||
@@ -787,6 +787,16 @@ func AdvancedActionsTestHandler(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if IP is already blocked before attempting action (for block action only)
|
||||||
|
skipLoggingIfAlreadyBlocked := false
|
||||||
|
if action == "block" && settings.AdvancedActions.Integration != "" {
|
||||||
|
active, checkErr := storage.IsPermanentBlockActive(c.Request.Context(), req.IP, settings.AdvancedActions.Integration)
|
||||||
|
if checkErr == nil && active {
|
||||||
|
// IP is already blocked, we'll check the error message after the call
|
||||||
|
skipLoggingIfAlreadyBlocked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err := runAdvancedIntegrationAction(
|
err := runAdvancedIntegrationAction(
|
||||||
c.Request.Context(),
|
c.Request.Context(),
|
||||||
action,
|
action,
|
||||||
@@ -794,8 +804,20 @@ func AdvancedActionsTestHandler(c *gin.Context) {
|
|||||||
settings,
|
settings,
|
||||||
server,
|
server,
|
||||||
map[string]any{"manual": true},
|
map[string]any{"manual": true},
|
||||||
|
skipLoggingIfAlreadyBlocked,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
// Check if error indicates IP is already blocked - show as info instead of error
|
||||||
|
if skipLoggingIfAlreadyBlocked {
|
||||||
|
errMsg := strings.ToLower(err.Error())
|
||||||
|
if strings.Contains(errMsg, "already have such entry") ||
|
||||||
|
strings.Contains(errMsg, "already exists") ||
|
||||||
|
strings.Contains(errMsg, "duplicate") {
|
||||||
|
// IP is already blocked, return info message with original error
|
||||||
|
c.JSON(http.StatusOK, gin.H{"message": err.Error(), "info": true})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -366,7 +366,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex gap-2">
|
<div class="flex gap-2">
|
||||||
<button type="button" class="px-3 py-2 text-sm rounded border border-gray-300 text-gray-700 hover:bg-gray-50" onclick="refreshPermanentBlockLog()" data-i18n="settings.advanced.refresh_log">Refresh Log</button>
|
<button type="button" class="px-3 py-2 text-sm rounded border border-gray-300 text-gray-700 hover:bg-gray-50" onclick="refreshPermanentBlockLog()" data-i18n="settings.advanced.refresh_log">Refresh Log</button>
|
||||||
<button type="button" class="px-3 py-2 text-sm rounded border border-blue-600 text-blue-600 hover:bg-blue-50" onclick="openAdvancedTestModal()" data-i18n="settings.advanced.test_button">Test Integration</button>
|
<button type="button" class="px-3 py-2 text-sm rounded border border-blue-600 text-blue-600 hover:bg-blue-50" onclick="openAdvancedTestModal()" data-i18n="settings.advanced.test_button">Manually Block / Test</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -975,7 +975,7 @@
|
|||||||
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
<div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
|
||||||
<div class="sm:flex sm:items-start">
|
<div class="sm:flex sm:items-start">
|
||||||
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left w-full">
|
||||||
<h3 class="text-lg leading-6 font-medium text-gray-900" data-i18n="settings.advanced.test_title">Test Advanced Integration</h3>
|
<h3 class="text-lg leading-6 font-medium text-gray-900" data-i18n="settings.advanced.test_title">Manually Block / Test</h3>
|
||||||
<div class="mt-4 space-y-4">
|
<div class="mt-4 space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label for="advancedTestIP" class="block text-sm font-medium text-gray-700" data-i18n="settings.advanced.test_ip">IP address</label>
|
<label for="advancedTestIP" class="block text-sm font-medium text-gray-700" data-i18n="settings.advanced.test_ip">IP address</label>
|
||||||
@@ -993,7 +993,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse gap-3">
|
<div class="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse gap-3">
|
||||||
<button type="button" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm" onclick="submitAdvancedTest('block')" data-i18n="settings.advanced.test_block">Block IP</button>
|
<button type="button" class="w-full inline-flex justify-center rounded-md border border-transparent shadow-sm px-4 py-2 bg-blue-600 text-base font-medium text-white hover:bg-blue-700 focus:outline-none sm:ml-3 sm:w-auto sm:text-sm" onclick="submitAdvancedTest('block')" data-i18n="settings.advanced.test_block">Block IP</button>
|
||||||
<button type="button" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm" onclick="submitAdvancedTest('unblock')" data-i18n="settings.advanced.test_unblock">Remove IP</button>
|
|
||||||
<button type="button" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm" onclick="closeModal('advancedTestModal')" data-i18n="modal.close">Close</button>
|
<button type="button" class="mt-3 w-full inline-flex justify-center rounded-md border border-gray-300 shadow-sm px-4 py-2 bg-white text-base font-medium text-gray-700 hover:bg-gray-50 focus:outline-none sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm" onclick="closeModal('advancedTestModal')" data-i18n="modal.close">Close</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -3468,7 +3467,7 @@
|
|||||||
<td class="px-3 py-2 text-xs text-gray-500">${escapeHtml(block.serverId || '')}</td>
|
<td class="px-3 py-2 text-xs text-gray-500">${escapeHtml(block.serverId || '')}</td>
|
||||||
<td class="px-3 py-2 text-xs text-gray-500">${block.updatedAt ? new Date(block.updatedAt).toLocaleString() : ''}</td>
|
<td class="px-3 py-2 text-xs text-gray-500">${block.updatedAt ? new Date(block.updatedAt).toLocaleString() : ''}</td>
|
||||||
<td class="px-3 py-2 text-right">
|
<td class="px-3 py-2 text-right">
|
||||||
<button class="text-sm text-blue-600 hover:text-blue-800" onclick="advancedUnblockIP('${escapeHtml(block.ip)}')" data-i18n="settings.advanced.unblock_btn">Remove</button>
|
<button type="button" class="text-sm text-blue-600 hover:text-blue-800" onclick="advancedUnblockIP('${escapeHtml(block.ip)}', event)" data-i18n="settings.advanced.unblock_btn">Remove</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
@@ -3537,7 +3536,9 @@
|
|||||||
if (data.error) {
|
if (data.error) {
|
||||||
showToast('Advanced action failed: ' + data.error, 'error');
|
showToast('Advanced action failed: ' + data.error, 'error');
|
||||||
} else {
|
} else {
|
||||||
showToast(data.message || 'Action completed', 'success');
|
// Check if this is an info message (e.g., IP already blocked)
|
||||||
|
const toastType = data.info ? 'info' : 'success';
|
||||||
|
showToast(data.message || 'Action completed', toastType);
|
||||||
loadPermanentBlockLog();
|
loadPermanentBlockLog();
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -3548,7 +3549,11 @@
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function advancedUnblockIP(ip) {
|
function advancedUnblockIP(ip, event) {
|
||||||
|
if (event) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
}
|
||||||
if (!ip) return;
|
if (!ip) return;
|
||||||
fetch('/api/advanced-actions/test', {
|
fetch('/api/advanced-actions/test', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
Reference in New Issue
Block a user