mirror of
https://github.com/swissmakers/fail2ban-ui.git
synced 2026-04-11 13:47:05 +02:00
Added manual block section and BanIP method to for all connectors, like the UnbanIP functionallity
This commit is contained in:
@@ -101,6 +101,11 @@ func (ac *AgentConnector) UnbanIP(ctx context.Context, jail, ip string) error {
|
||||
return ac.post(ctx, fmt.Sprintf("/v1/jails/%s/unban", url.PathEscape(jail)), payload, nil)
|
||||
}
|
||||
|
||||
func (ac *AgentConnector) BanIP(ctx context.Context, jail, ip string) error {
|
||||
payload := map[string]string{"ip": ip}
|
||||
return ac.post(ctx, fmt.Sprintf("/v1/jails/%s/ban", url.PathEscape(jail)), payload, nil)
|
||||
}
|
||||
|
||||
func (ac *AgentConnector) Reload(ctx context.Context) error {
|
||||
return ac.post(ctx, "/v1/actions/reload", nil, nil)
|
||||
}
|
||||
|
||||
@@ -140,6 +140,15 @@ func (lc *LocalConnector) UnbanIP(ctx context.Context, jail, ip string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// BanIP implements Connector.
|
||||
func (lc *LocalConnector) BanIP(ctx context.Context, jail, ip string) error {
|
||||
args := []string{"set", jail, "banip", ip}
|
||||
if _, err := lc.runFail2banClient(ctx, args...); err != nil {
|
||||
return fmt.Errorf("error banning IP %s in jail %s: %w", ip, jail, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reload implements Connector.
|
||||
func (lc *LocalConnector) Reload(ctx context.Context) error {
|
||||
out, err := lc.runFail2banClient(ctx, "reload")
|
||||
|
||||
@@ -187,6 +187,11 @@ func (sc *SSHConnector) UnbanIP(ctx context.Context, jail, ip string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (sc *SSHConnector) BanIP(ctx context.Context, jail, ip string) error {
|
||||
_, err := sc.runFail2banCommand(ctx, "set", jail, "banip", ip)
|
||||
return err
|
||||
}
|
||||
|
||||
func (sc *SSHConnector) Reload(ctx context.Context) error {
|
||||
_, err := sc.runFail2banCommand(ctx, "reload")
|
||||
return err
|
||||
|
||||
@@ -16,6 +16,7 @@ type Connector interface {
|
||||
GetJailInfos(ctx context.Context) ([]JailInfo, error)
|
||||
GetBannedIPs(ctx context.Context, jail string) ([]string, error)
|
||||
UnbanIP(ctx context.Context, jail, ip string) error
|
||||
BanIP(ctx context.Context, jail, ip string) error
|
||||
Reload(ctx context.Context) error
|
||||
Restart(ctx context.Context) error
|
||||
GetFilterConfig(ctx context.Context, jail string) (string, string, error) // Returns (config, filePath, error)
|
||||
|
||||
@@ -38,6 +38,20 @@
|
||||
"dashboard.table.log_line": "Logzeile",
|
||||
"dashboard.no_banned_ips": "Keine gesperrten IPs",
|
||||
"dashboard.unban": "Entsperren",
|
||||
"dashboard.manual_block.title": "Manuelle IP-Sperre",
|
||||
"dashboard.manual_block.subtitle": "Manuell eine IP-Adresse in einem bestimmten Jail sperren.",
|
||||
"dashboard.manual_block.expand_hint": "Klicken Sie, um zu erweitern und eine IP-Adresse zu sperren",
|
||||
"dashboard.manual_block.jail_label": "Jail auswählen",
|
||||
"dashboard.manual_block.jail_placeholder": "Jail auswählen...",
|
||||
"dashboard.manual_block.ip_label": "IP-Adresse",
|
||||
"dashboard.manual_block.ip_placeholder": "z.B. 88.76.21.123",
|
||||
"dashboard.manual_block.button": "IP sperren",
|
||||
"dashboard.manual_block.confirm": "IP {ip} im Jail {jail} sperren?",
|
||||
"dashboard.manual_block.success": "IP erfolgreich gesperrt",
|
||||
"dashboard.manual_block.error": "Fehler beim Sperren der IP",
|
||||
"dashboard.manual_block.jail_required": "Bitte wählen Sie ein Jail aus",
|
||||
"dashboard.manual_block.ip_required": "Bitte geben Sie eine IP-Adresse ein",
|
||||
"dashboard.manual_block.invalid_ip": "Bitte geben Sie eine gültige IP-Adresse ein",
|
||||
"dashboard.banned.show_more": "Mehr anzeigen",
|
||||
"dashboard.banned.show_less": "Weniger anzeigen",
|
||||
"logs.overview.title": "Interne Log-Übersicht",
|
||||
|
||||
@@ -38,6 +38,20 @@
|
||||
"dashboard.table.log_line": "Log-Zile",
|
||||
"dashboard.no_banned_ips": "Ke g'sperrti IPs",
|
||||
"dashboard.unban": "Entsperre",
|
||||
"dashboard.manual_block.title": "Manuelli IP-Sperri",
|
||||
"dashboard.manual_block.subtitle": "Manuell e IP-Adrässe inme bestimmte Jail sperre.",
|
||||
"dashboard.manual_block.expand_hint": "Klick zum Ufklappe und e IP-Adrässe z sperre",
|
||||
"dashboard.manual_block.jail_label": "Jail uswähle",
|
||||
"dashboard.manual_block.jail_placeholder": "Jail uswähle...",
|
||||
"dashboard.manual_block.ip_label": "IP-Adrässe",
|
||||
"dashboard.manual_block.ip_placeholder": "z.B. 88.76.21.123",
|
||||
"dashboard.manual_block.button": "IP sperre",
|
||||
"dashboard.manual_block.confirm": "IP {ip} im Jail {jail} sperre?",
|
||||
"dashboard.manual_block.success": "IP erfolgriich g'sperrt",
|
||||
"dashboard.manual_block.error": "Fehler bim Sperre vo dr IP",
|
||||
"dashboard.manual_block.jail_required": "Bitte wähl es Jail us",
|
||||
"dashboard.manual_block.ip_required": "Bitte gib e IP-Adrässe ii",
|
||||
"dashboard.manual_block.invalid_ip": "Bitte gib e gültigi IP-Adrässe ii",
|
||||
"dashboard.banned.show_more": "Meh azeige",
|
||||
"dashboard.banned.show_less": "Weniger azeige",
|
||||
"logs.overview.title": "Generelli Log-Übersicht",
|
||||
|
||||
@@ -38,6 +38,20 @@
|
||||
"dashboard.table.log_line": "Log Line",
|
||||
"dashboard.no_banned_ips": "No banned IPs",
|
||||
"dashboard.unban": "Unban",
|
||||
"dashboard.manual_block.title": "Manual Block IP",
|
||||
"dashboard.manual_block.subtitle": "Manually block an IP address in a specific jail.",
|
||||
"dashboard.manual_block.expand_hint": "Click to expand and block an IP address",
|
||||
"dashboard.manual_block.jail_label": "Select Jail",
|
||||
"dashboard.manual_block.jail_placeholder": "Choose a jail...",
|
||||
"dashboard.manual_block.ip_label": "IP Address",
|
||||
"dashboard.manual_block.ip_placeholder": "e.g., 88.76.21.123",
|
||||
"dashboard.manual_block.button": "Block IP",
|
||||
"dashboard.manual_block.confirm": "Block IP {ip} in jail {jail}?",
|
||||
"dashboard.manual_block.success": "IP blocked successfully",
|
||||
"dashboard.manual_block.error": "Error blocking IP",
|
||||
"dashboard.manual_block.jail_required": "Please select a jail",
|
||||
"dashboard.manual_block.ip_required": "Please enter an IP address",
|
||||
"dashboard.manual_block.invalid_ip": "Please enter a valid IP address",
|
||||
"dashboard.banned.show_more": "Show more",
|
||||
"dashboard.banned.show_less": "Hide extra",
|
||||
"logs.overview.title": "Internal Log Overview",
|
||||
|
||||
@@ -38,6 +38,20 @@
|
||||
"dashboard.table.log_line": "Línea de log",
|
||||
"dashboard.no_banned_ips": "No hay IP bloqueadas",
|
||||
"dashboard.unban": "Desbloquear",
|
||||
"dashboard.manual_block.title": "Bloqueo manual de IP",
|
||||
"dashboard.manual_block.subtitle": "Bloquear manualmente una dirección IP en una cárcel específica.",
|
||||
"dashboard.manual_block.expand_hint": "Haga clic para expandir y bloquear una dirección IP",
|
||||
"dashboard.manual_block.jail_label": "Seleccionar cárcel",
|
||||
"dashboard.manual_block.jail_placeholder": "Elegir una cárcel...",
|
||||
"dashboard.manual_block.ip_label": "Dirección IP",
|
||||
"dashboard.manual_block.ip_placeholder": "ej. 88.76.21.123",
|
||||
"dashboard.manual_block.button": "Bloquear IP",
|
||||
"dashboard.manual_block.confirm": "¿Bloquear IP {ip} en la cárcel {jail}?",
|
||||
"dashboard.manual_block.success": "IP bloqueada exitosamente",
|
||||
"dashboard.manual_block.error": "Error al bloquear la IP",
|
||||
"dashboard.manual_block.jail_required": "Por favor seleccione una cárcel",
|
||||
"dashboard.manual_block.ip_required": "Por favor ingrese una dirección IP",
|
||||
"dashboard.manual_block.invalid_ip": "Por favor ingrese una dirección IP válida",
|
||||
"dashboard.banned.show_more": "Mostrar más",
|
||||
"dashboard.banned.show_less": "Mostrar menos",
|
||||
"logs.overview.title": "Resumen interno de registros",
|
||||
|
||||
@@ -38,6 +38,20 @@
|
||||
"dashboard.table.log_line": "Ligne de log",
|
||||
"dashboard.no_banned_ips": "Aucune IP bloquée",
|
||||
"dashboard.unban": "Débloquer",
|
||||
"dashboard.manual_block.title": "Blocage manuel d'IP",
|
||||
"dashboard.manual_block.subtitle": "Bloquer manuellement une adresse IP dans une prison spécifique.",
|
||||
"dashboard.manual_block.expand_hint": "Cliquez pour développer et bloquer une adresse IP",
|
||||
"dashboard.manual_block.jail_label": "Sélectionner une prison",
|
||||
"dashboard.manual_block.jail_placeholder": "Choisir une prison...",
|
||||
"dashboard.manual_block.ip_label": "Adresse IP",
|
||||
"dashboard.manual_block.ip_placeholder": "ex. 88.76.21.123",
|
||||
"dashboard.manual_block.button": "Bloquer l'IP",
|
||||
"dashboard.manual_block.confirm": "Bloquer l'IP {ip} dans la prison {jail}?",
|
||||
"dashboard.manual_block.success": "IP bloquée avec succès",
|
||||
"dashboard.manual_block.error": "Erreur lors du blocage de l'IP",
|
||||
"dashboard.manual_block.jail_required": "Veuillez sélectionner une prison",
|
||||
"dashboard.manual_block.ip_required": "Veuillez entrer une adresse IP",
|
||||
"dashboard.manual_block.invalid_ip": "Veuillez entrer une adresse IP valide",
|
||||
"dashboard.banned.show_more": "Afficher plus",
|
||||
"dashboard.banned.show_less": "Afficher moins",
|
||||
"logs.overview.title": "Vue d'ensemble interne des journaux",
|
||||
|
||||
@@ -38,6 +38,20 @@
|
||||
"dashboard.table.log_line": "Riga di log",
|
||||
"dashboard.no_banned_ips": "Nessuna IP bloccata",
|
||||
"dashboard.unban": "Sblocca",
|
||||
"dashboard.manual_block.title": "Blocco manuale IP",
|
||||
"dashboard.manual_block.subtitle": "Bloccare manualmente un indirizzo IP in una prigione specifica.",
|
||||
"dashboard.manual_block.expand_hint": "Fare clic per espandere e bloccare un indirizzo IP",
|
||||
"dashboard.manual_block.jail_label": "Seleziona prigione",
|
||||
"dashboard.manual_block.jail_placeholder": "Scegli una prigione...",
|
||||
"dashboard.manual_block.ip_label": "Indirizzo IP",
|
||||
"dashboard.manual_block.ip_placeholder": "es. 88.76.21.123",
|
||||
"dashboard.manual_block.button": "Blocca IP",
|
||||
"dashboard.manual_block.confirm": "Bloccare IP {ip} nella prigione {jail}?",
|
||||
"dashboard.manual_block.success": "IP bloccato con successo",
|
||||
"dashboard.manual_block.error": "Errore nel bloccare l'IP",
|
||||
"dashboard.manual_block.jail_required": "Si prega di selezionare una prigione",
|
||||
"dashboard.manual_block.ip_required": "Si prega di inserire un indirizzo IP",
|
||||
"dashboard.manual_block.invalid_ip": "Si prega di inserire un indirizzo IP valido",
|
||||
"dashboard.banned.show_more": "Mostra di più",
|
||||
"dashboard.banned.show_less": "Mostra meno",
|
||||
"logs.overview.title": "Panoramica interna dei log",
|
||||
|
||||
@@ -170,6 +170,29 @@ func UnbanIPHandler(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// BanIPHandler bans a given IP in a specific jail.
|
||||
func BanIPHandler(c *gin.Context) {
|
||||
config.DebugLog("----------------------------")
|
||||
config.DebugLog("BanIPHandler called (handlers.go)") // entry point
|
||||
jail := c.Param("jail")
|
||||
ip := c.Param("ip")
|
||||
|
||||
conn, err := resolveConnector(c)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if err := conn.BanIP(c.Request.Context(), jail, ip); err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
fmt.Println(ip + " in jail " + jail + " banned successfully.")
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"message": "IP banned successfully",
|
||||
})
|
||||
}
|
||||
|
||||
// BanNotificationHandler processes incoming ban notifications from Fail2Ban.
|
||||
func BanNotificationHandler(c *gin.Context) {
|
||||
// Validate callback secret
|
||||
|
||||
@@ -32,6 +32,7 @@ func RegisterRoutes(r *gin.Engine, hub *Hub) {
|
||||
{
|
||||
api.GET("/summary", SummaryHandler)
|
||||
api.POST("/jails/:jail/unban/:ip", UnbanIPHandler)
|
||||
api.POST("/jails/:jail/ban/:ip", BanIPHandler)
|
||||
|
||||
// Routes for jail-filter management (TODO: rename API-call)
|
||||
api.GET("/jails/:jail/config", GetJailFilterConfigHandler)
|
||||
|
||||
@@ -441,6 +441,24 @@ function toggleBannedList(hiddenId, buttonId) {
|
||||
}
|
||||
}
|
||||
|
||||
function toggleManualBlockSection() {
|
||||
var container = document.getElementById('manualBlockFormContainer');
|
||||
var icon = document.getElementById('manualBlockToggleIcon');
|
||||
if (!container || !icon) {
|
||||
return;
|
||||
}
|
||||
var isHidden = container.classList.contains("hidden");
|
||||
if (isHidden) {
|
||||
container.classList.remove("hidden");
|
||||
icon.classList.remove("fa-chevron-down");
|
||||
icon.classList.add("fa-chevron-up");
|
||||
} else {
|
||||
container.classList.add("hidden");
|
||||
icon.classList.remove("fa-chevron-up");
|
||||
icon.classList.add("fa-chevron-down");
|
||||
}
|
||||
}
|
||||
|
||||
function unbanIP(jail, ip) {
|
||||
const confirmMsg = isLOTRModeActive
|
||||
? 'Restore ' + ip + ' to the realm from ' + jail + '?'
|
||||
@@ -470,6 +488,75 @@ function unbanIP(jail, ip) {
|
||||
});
|
||||
}
|
||||
|
||||
function banIP(jail, ip) {
|
||||
const confirmMsg = isLOTRModeActive
|
||||
? 'Banish ' + ip + ' from the realm in ' + jail + '?'
|
||||
: 'Block IP ' + ip + ' in jail ' + jail + '?';
|
||||
if (!confirm(confirmMsg)) {
|
||||
return;
|
||||
}
|
||||
showLoading(true);
|
||||
var url = '/api/jails/' + encodeURIComponent(jail) + '/ban/' + encodeURIComponent(ip);
|
||||
fetch(withServerParam(url), {
|
||||
method: 'POST',
|
||||
headers: serverHeaders()
|
||||
})
|
||||
.then(function(res) { return res.json(); })
|
||||
.then(function(data) {
|
||||
if (data.error) {
|
||||
showToast("Error blocking IP: " + data.error, 'error');
|
||||
} else {
|
||||
showToast(t('dashboard.manual_block.success', 'IP blocked successfully'), 'success');
|
||||
return refreshData({ silent: true });
|
||||
}
|
||||
})
|
||||
.catch(function(err) {
|
||||
showToast("Error: " + err, 'error');
|
||||
})
|
||||
.finally(function() {
|
||||
showLoading(false);
|
||||
});
|
||||
}
|
||||
|
||||
function handleManualBlock() {
|
||||
var jailSelect = document.getElementById('blockJailSelect');
|
||||
var ipInput = document.getElementById('blockIPInput');
|
||||
|
||||
if (!jailSelect || !ipInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
var jail = jailSelect.value;
|
||||
var ip = ipInput.value.trim();
|
||||
|
||||
if (!jail) {
|
||||
showToast(t('dashboard.manual_block.jail_required', 'Please select a jail'), 'error');
|
||||
jailSelect.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ip) {
|
||||
showToast(t('dashboard.manual_block.ip_required', 'Please enter an IP address'), 'error');
|
||||
ipInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// Basic IP validation
|
||||
var ipv4Pattern = /^([0-9]{1,3}\.){3}[0-9]{1,3}$/;
|
||||
var ipv6Pattern = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
|
||||
if (!ipv4Pattern.test(ip) && !ipv6Pattern.test(ip)) {
|
||||
showToast(t('dashboard.manual_block.invalid_ip', 'Please enter a valid IP address'), 'error');
|
||||
ipInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
banIP(jail, ip);
|
||||
|
||||
// Clear form after submission
|
||||
ipInput.value = '';
|
||||
jailSelect.value = '';
|
||||
}
|
||||
|
||||
function renderDashboard() {
|
||||
var container = document.getElementById('dashboard');
|
||||
if (!container) return;
|
||||
@@ -590,6 +677,56 @@ function renderDashboard() {
|
||||
html += '</div>'; // close overview card
|
||||
}
|
||||
|
||||
// Manual Block IP Section
|
||||
if (summary && summary.jails && summary.jails.length > 0) {
|
||||
var enabledJails = summary.jails.filter(function(j) { return j.enabled !== false; });
|
||||
if (enabledJails.length > 0) {
|
||||
html += ''
|
||||
+ '<div class="bg-white rounded-lg shadow p-6 mb-6">'
|
||||
+ ' <div class="cursor-pointer hover:bg-gray-50 -m-6 p-6 rounded-lg transition-colors" onclick="toggleManualBlockSection()">'
|
||||
+ ' <div class="flex items-center justify-between">'
|
||||
+ ' <div class="flex-1">'
|
||||
+ ' <h3 class="text-lg font-medium text-gray-900 mb-2" data-i18n="dashboard.manual_block.title">Manual Block IP</h3>'
|
||||
+ ' <p class="text-sm text-gray-500" data-i18n="dashboard.manual_block.subtitle">Manually block an IP address in a specific jail.</p>'
|
||||
+ ' <p class="text-xs text-gray-400 mt-1" data-i18n="dashboard.manual_block.expand_hint">Click to expand and block an IP address</p>'
|
||||
+ ' </div>'
|
||||
+ ' <div class="ml-4">'
|
||||
+ ' <i id="manualBlockToggleIcon" class="fas fa-chevron-down text-gray-400 transition-transform"></i>'
|
||||
+ ' </div>'
|
||||
+ ' </div>'
|
||||
+ ' </div>'
|
||||
+ ' <div id="manualBlockFormContainer" class="hidden mt-4">'
|
||||
+ ' <form id="manualBlockForm" onsubmit="return false;">'
|
||||
+ ' <div class="grid grid-cols-1 md:grid-cols-3 gap-4">'
|
||||
+ ' <div>'
|
||||
+ ' <label for="blockJailSelect" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="dashboard.manual_block.jail_label">Select Jail</label>'
|
||||
+ ' <select id="blockJailSelect" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" required>'
|
||||
+ ' <option value="" data-i18n="dashboard.manual_block.jail_placeholder">Choose a jail...</option>';
|
||||
|
||||
enabledJails.forEach(function(jail) {
|
||||
html += ' <option value="' + escapeHtml(jail.jailName) + '">' + escapeHtml(jail.jailName) + '</option>';
|
||||
});
|
||||
|
||||
html += ''
|
||||
+ ' </select>'
|
||||
+ ' </div>'
|
||||
+ ' <div>'
|
||||
+ ' <label for="blockIPInput" class="block text-sm font-medium text-gray-700 mb-2" data-i18n="dashboard.manual_block.ip_label">IP Address</label>'
|
||||
+ ' <input type="text" id="blockIPInput" class="w-full border border-gray-300 rounded-md px-3 py-2 focus:outline-none focus:ring-2 focus:ring-blue-500" data-i18n-placeholder="dashboard.manual_block.ip_placeholder" placeholder="e.g., 88.76.21.123" pattern="^([0-9]{1,3}\\.){3}[0-9]{1,3}$|^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$" required>'
|
||||
+ ' </div>'
|
||||
+ ' <div class="flex items-end">'
|
||||
+ ' <button type="button" onclick="handleManualBlock()" class="w-full bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition-colors flex items-center justify-center gap-2">'
|
||||
+ ' <i class="fas fa-ban"></i>'
|
||||
+ ' <span data-i18n="dashboard.manual_block.button">Block IP</span>'
|
||||
+ ' </button>'
|
||||
+ ' </div>'
|
||||
+ ' </div>'
|
||||
+ ' </form>'
|
||||
+ ' </div>'
|
||||
+ '</div>';
|
||||
}
|
||||
}
|
||||
|
||||
html += '<div id="logOverview">' + renderLogOverviewContent() + '</div>';
|
||||
|
||||
container.innerHTML = html;
|
||||
|
||||
Reference in New Issue
Block a user