2025-01-25 16:21:14 +01:00
<!DOCTYPE html>
< html lang = "en" >
2025-01-26 20:11:06 +01:00
2025-01-25 16:21:14 +01:00
< head >
< meta charset = "UTF-8" / >
2025-01-26 20:11:06 +01:00
< meta name = "viewport" content = "width=device-width, initial-scale=1.0, user-scalable=no" / >
2025-01-25 16:21:14 +01:00
< title > Fail2ban UI Dashboard< / title >
<!-- Bootstrap 5 (CDN) -->
< link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
rel="stylesheet"
/>
< style >
/* Loading overlay styling */
#loading-overlay {
display: none; /* hidden by default */
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.5);
z-index: 9999; /* on top */
align-items: center;
justify-content: center;
}
.spinner-border {
width: 4rem; height: 4rem;
}
/* Reload banner */
#reloadBanner {
display: none;
}
< / style >
< / head >
2025-01-26 20:11:06 +01:00
2025-01-25 16:21:14 +01:00
< body class = "bg-light" >
2025-01-26 20:11:06 +01:00
<!-- ******************************************************************* -->
<!-- NAVIGATION : -->
<!-- ******************************************************************* -->
2025-01-25 16:21:14 +01:00
< nav class = "navbar navbar-expand-lg navbar-dark bg-primary" >
2025-01-25 21:43:50 +01:00
< div class = "container-fluid" >
< a class = "navbar-brand" href = "#" >
< strong > Fail2ban UI< / strong >
< / a >
< button class = "navbar-toggler" type = "button" data-bs-toggle = "collapse"
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation">
< span class = "navbar-toggler-icon" > < / span >
< / button >
< div class = "collapse navbar-collapse" id = "navbarSupportedContent" >
< ul class = "navbar-nav ms-auto mb-2 mb-lg-0" >
< li class = "nav-item" >
< a class = "nav-link" href = "#" onclick = "showSection('dashboardSection')" > Dashboard< / a >
< / li >
< li class = "nav-item" >
< a class = "nav-link" href = "#" onclick = "showSection('filterSection')" > Filter Debug< / a >
< / li >
< li class = "nav-item" >
< a class = "nav-link" href = "#" onclick = "showSection('settingsSection')" > Settings< / a >
< / li >
< / ul >
< / div >
< / div >
2025-01-26 20:11:06 +01:00
< / nav >
<!-- ******************************************************************* -->
2025-01-25 16:21:14 +01:00
<!-- Reload Banner -->
< div id = "reloadBanner" class = "bg-warning text-dark p-3 text-center" >
< strong > Configuration changed! < / strong >
< button class = "btn btn-dark" onclick = "reloadFail2ban()" >
Reload Fail2ban
< / button >
< / div >
2025-01-26 20:11:06 +01:00
<!-- ******************************************************************* -->
<!-- APP Sections (Pages) : -->
<!-- ******************************************************************* -->
2025-01-25 21:43:50 +01:00
<!-- Dashboard Section -->
< div id = "dashboardSection" class = "container my-4" >
2025-01-25 16:21:14 +01:00
< h1 class = "mb-4" > Dashboard< / h1 >
< div id = "dashboard" > < / div >
< / div >
2025-01-26 20:11:06 +01:00
<!-- Filter Debug Section -->
< div id = "filterSection" style = "display: none;" class = "container my-4" >
< h2 > Filter Debug< / h2 >
<!-- Dropdown of available jail/filters -->
< div class = "mb-3" >
< label for = "filterSelect" class = "form-label" > Select a Filter< / label >
< select id = "filterSelect" class = "form-select" > < / select >
< / div >
<!-- Textarea for log lines to test -->
< div class = "mb-3" >
< label class = "form-label" > Log Lines< / label >
< textarea id = "logLinesTextarea" class = "form-control" rows = "6" disabled > < / textarea >
< / div >
< button class = "btn btn-secondary" onclick = "testSelectedFilter()" > Test Filter< / button >
< hr / >
< div id = "testResults" > < / div >
< / div >
2025-01-25 21:43:50 +01:00
<!-- Settings Section -->
< div id = "settingsSection" style = "display: none;" class = "container my-4" >
2025-01-27 11:09:06 +01:00
< h2 > Settings< / h2 >
< form onsubmit = "saveSettings(event)" >
<!-- General Settings Group -->
< fieldset class = "border p-3 rounded mb-4" >
< legend class = "w-auto px-2" > General Settings< / legend >
<!-- Language Selection -->
2025-01-25 21:43:50 +01:00
< div class = "mb-3" >
< label for = "languageSelect" class = "form-label" > Language< / label >
2025-01-26 20:11:06 +01:00
< select id = "languageSelect" class = "form-select" disabled >
2025-01-25 21:43:50 +01:00
< option value = "en" > English< / option >
< option value = "de" > Deutsch< / option >
< / select >
< / div >
2025-01-27 11:09:06 +01:00
<!-- Debug Log Output -->
< div class = "mb-3 form-check" >
< input type = "checkbox" class = "form-check-input" id = "debugMode" >
< label for = "debugMode" class = "form-check-label" > Enable Debug Log< / label >
< / div >
< / fieldset >
<!-- Alert Settings Group -->
< fieldset class = "border p-3 rounded mb-4" >
< legend class = "w-auto px-2" > Alert Settings< / legend >
<!-- Source Email -->
< div class = "mb-3" >
< label for = "sourceEmail" class = "form-label" > Source Email - Emails are sent from this address.< / label >
< input type = "email" class = "form-control" id = "sourceEmail" / >
< / div >
<!-- Destination Email -->
2025-01-25 21:43:50 +01:00
< div class = "mb-3" >
2025-01-27 11:09:06 +01:00
< label for = "destEmail" class = "form-label" > Destination Email - Where to sent the alert messages?< / label >
< input type = "email" class = "form-control" id = "destEmail" placeholder = "e.g., alerts@swissmakers.ch" / >
2025-01-25 21:43:50 +01:00
< / div >
2025-01-27 11:09:06 +01:00
<!-- Alert Countries -->
2025-01-25 21:43:50 +01:00
< div class = "mb-3" >
2025-01-26 20:11:06 +01:00
< label for = "alertCountries" class = "form-label" > Select alert Countries< / label >
2025-01-27 11:09:06 +01:00
< p class = "text-muted" > Choose which country IP blocks should trigger an email. You can select multiple with CTRL.< / p >
2025-01-26 20:11:06 +01:00
< select id = "alertCountries" class = "form-select" multiple size = "7" >
< option value = "ALL" > ALL (Every Country)< / option >
< option value = "CH" > Switzerland (CH)< / option >
< option value = "DE" > Germany (DE)< / option >
< option value = "IT" > Italy (IT)< / option >
< option value = "FR" > France (FR)< / option >
< option value = "UK" > England (UK)< / option >
< option value = "US" > United States (US)< / option >
<!-- Maybe i will add more later.. -->
< / select >
2025-01-25 21:43:50 +01:00
< / div >
2025-01-27 11:09:06 +01:00
< / fieldset >
<!-- Fail2Ban Configuration Group -->
< fieldset class = "border p-3 rounded mb-4" >
< legend class = "w-auto px-2" > Fail2Ban Configuration< / legend >
<!-- Bantime Increment -->
< div class = "mb-3 form-check" >
< input type = "checkbox" class = "form-check-input" id = "bantimeIncrement" / >
< label for = "bantimeIncrement" class = "form-check-label" > Enable Bantime Increment< / label >
< / div >
<!-- Bantime -->
< div class = "mb-3" >
< label for = "banTime" class = "form-label" > Default Bantime< / label >
< input type = "text" class = "form-control" id = "banTime" placeholder = "e.g., 48h" / >
< / div >
<!-- Findtime -->
< div class = "mb-3" >
< label for = "findTime" class = "form-label" > Default Findtime< / label >
< input type = "text" class = "form-control" id = "findTime" placeholder = "e.g., 30m" / >
< / div >
<!-- Max Retry -->
< div class = "mb-3" >
< label for = "maxRetry" class = "form-label" > Default Max Retry< / label >
< input type = "number" class = "form-control" id = "maxRetry" placeholder = "Enter maximum retries" / >
< / div >
<!-- Ignore IPs -->
< div class = "mb-3" >
< label for = "ignoreIP" class = "form-label" > Ignore IPs< / label >
< textarea class = "form-control" id = "ignoreIP" rows = "2" placeholder = "Enter IPs to ignore, separated by spaces" > < / textarea >
< / div >
< / fieldset >
< button type = "submit" class = "btn btn-primary" > Save< / button >
< / form >
< / div >
2025-01-26 20:11:06 +01:00
<!-- ******************************************************************* -->
2025-01-25 21:43:50 +01:00
2025-01-25 16:21:14 +01:00
<!-- Footer -->
< footer class = "text-center mt-4 mb-4" >
< p class = "mb-0" >
© < a href = "https://swissmakers.ch" target = "_blank" > Swissmakers GmbH< / a >
-
< a href = "https://github.com/swissmakers/fail2ban-ui" target = "_blank" >
GitHub
< / a >
< / p >
< / footer >
2025-01-26 20:11:06 +01:00
<!-- ******************************************************************* -->
<!-- APP Components (HTML) : -->
<!-- ******************************************************************* -->
2025-01-25 16:21:14 +01:00
<!-- Loading Overlay -->
< div id = "loading-overlay" class = "d-flex" >
< div class = "spinner-border text-light" role = "status" >
< span class = "visually-hidden" > Loading...< / span >
< / div >
< / div >
<!-- Jail Config Modal -->
< div class = "modal fade" id = "jailConfigModal" tabindex = "-1" aria-hidden = "true" >
< div class = "modal-dialog modal-lg modal-dialog-scrollable" >
< div class = "modal-content" >
< div class = "modal-header" >
< h5 class = "modal-title" >
Filter Config: < span id = "modalJailName" > < / span >
< / h5 >
< button type = "button" class = "btn-close" data-bs-dismiss = "modal"
aria-label="Close">< / button >
< / div >
< div class = "modal-body" >
< textarea id = "jailConfigTextarea" class = "form-control" rows = "15" > < / textarea >
< / div >
< div class = "modal-footer" >
< button type = "button" class = "btn btn-secondary"
data-bs-dismiss="modal">Cancel< / button >
< button type = "button" class = "btn btn-primary" onclick = "saveJailConfig()" >
Save
< / button >
< / div >
< / div >
< / div >
< / div >
2025-01-26 20:11:06 +01:00
<!-- ******************************************************************* -->
2025-01-25 16:21:14 +01:00
<!-- Bootstrap 5 JS (for modal, etc.) -->
< script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js">
< / script >
< script >
2025-01-26 20:11:06 +01:00
// For information: We avoid ES6 backticks in our JS, to prevent confusion with the Go template parser.
2025-01-25 16:21:14 +01:00
"use strict";
2025-01-26 20:11:06 +01:00
//*******************************************************************
//* Init page and main-components : *
//*******************************************************************
2025-01-25 16:21:14 +01:00
2025-01-26 20:11:06 +01:00
// Init and run first function, when DOM is ready
2025-01-25 16:21:14 +01:00
var currentJailForConfig = null;
2025-01-26 20:11:06 +01:00
window.addEventListener('DOMContentLoaded', function() {
showLoading(true);
checkReloadNeeded();
fetchSummary().then(function() {
showLoading(false);
2025-01-27 13:21:45 +01:00
initializeTooltips(); // Initialize tooltips after fetching and rendering
2025-01-26 20:11:06 +01:00
});
});
2025-01-25 16:21:14 +01:00
2025-01-27 13:21:45 +01:00
// Function to initialize Bootstrap tooltips
function initializeTooltips() {
const tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'));
tooltipTriggerList.forEach(function (tooltipTriggerEl) {
new bootstrap.Tooltip(tooltipTriggerEl);
});
}
2025-01-25 16:21:14 +01:00
// Toggle the loading overlay (with !important)
function showLoading(show) {
var overlay = document.getElementById('loading-overlay');
if (show) {
overlay.style.setProperty('display', 'flex', 'important');
} else {
overlay.style.setProperty('display', 'none', 'important');
}
}
2025-01-26 20:11:06 +01:00
// Check if there is still a reload of the fail2ban service needed
function checkReloadNeeded() {
fetch('/api/settings')
.then(res => res.json())
.then(data => {
if (data.reloadNeeded) {
document.getElementById('reloadBanner').style.display = 'block';
} else {
document.getElementById('reloadBanner').style.display = 'none';
}
})
.catch(err => console.error('Error checking reloadNeeded:', err));
}
// Load dynamically the other pages when navigating in nav
function showSection(sectionId) {
// hide all sections
document.getElementById('dashboardSection').style.display = 'none';
document.getElementById('filterSection').style.display = 'none';
document.getElementById('settingsSection').style.display = 'none';
// show the requested section
document.getElementById(sectionId).style.display = 'block';
// If it's filterSection, load filters
if (sectionId === 'filterSection') {
showFilterSection();
}
// If it's settingsSection, load settings
if (sectionId === 'settingsSection') {
loadSettings();
}
}
//*******************************************************************
//* Fetch data and render dashboard : *
//*******************************************************************
2025-01-25 16:21:14 +01:00
// Fetch summary (jails, stats, last 5 bans)
function fetchSummary() {
return fetch('/api/summary')
.then(function(res) { return res.json(); })
.then(function(data) {
if (data.error) {
document.getElementById('dashboard').innerHTML =
'< div class = "alert alert-danger" > ' + data.error + '< / div > ';
return;
}
renderDashboard(data);
})
.catch(function(err) {
document.getElementById('dashboard').innerHTML =
'< div class = "alert alert-danger" > Error: ' + err + '< / div > ';
});
}
// Render the main dashboard
function renderDashboard(data) {
var html = "";
2025-01-27 11:35:21 +01:00
// Add a search bar
html += `
< div class = "mb-3" >
< label for = "ipSearch" class = "form-label" > Search Banned IPs< / label >
< input type = "text" id = "ipSearch" class = "form-control" placeholder = "Enter IP address to search" onkeyup = "filterIPs()" >
< / div >
`;
2025-01-25 16:21:14 +01:00
// Jails table
if (!data.jails || data.jails.length === 0) {
html += '< p > No jails found.< / p > ';
} else {
html += ''
2025-01-27 13:21:45 +01:00
+ '< h2 > < span data-bs-toggle = "tooltip" data-bs-placement = "top" title = "The Overview displays the currently enabled jails that you have added to your jail.local configuration." > Overview< / span > < / h2 > '
2025-01-27 11:35:21 +01:00
+ '< table class = "table table-striped" id = "jailsTable" > '
2025-01-25 16:21:14 +01:00
+ ' < thead > '
+ ' < tr > '
+ ' < th > Jail Name< / th > '
+ ' < th > Total Banned< / th > '
2025-01-26 20:11:06 +01:00
+ ' < th > New Last Hour< / th > '
2025-01-25 16:21:14 +01:00
+ ' < th > Banned IPs (Unban)< / th > '
+ ' < / tr > '
+ ' < / thead > '
+ ' < tbody > ';
data.jails.forEach(function(jail) {
var bannedHTML = renderBannedIPs(jail.jailName, jail.bannedIPs);
html += ''
2025-01-27 11:35:21 +01:00
+ '< tr class = "jail-row" > '
2025-01-25 16:21:14 +01:00
+ ' < td > '
+ ' < a href = "#" onclick = "openJailConfigModal(\'' + jail.jailName + '\')" > '
+ jail.jailName
+ ' < / a > '
+ ' < / td > '
+ ' < td > ' + jail.totalBanned + '< / td > '
+ ' < td > ' + jail.newInLastHour + '< / td > '
+ ' < td > ' + bannedHTML + '< / td > '
+ '< / tr > ';
});
html += '< / tbody > < / table > ';
}
// Last 5 bans
html += '< h2 > Last 5 Ban Events< / h2 > ';
if (!data.lastBans || data.lastBans.length === 0) {
html += '< p > No recent bans found.< / p > ';
} else {
html += ''
+ '< table class = "table table-bordered" > '
+ ' < thead > '
+ ' < tr > '
+ ' < th > Time< / th > '
+ ' < th > Jail< / th > '
+ ' < th > IP< / th > '
+ ' < th > Log Line< / th > '
+ ' < / tr > '
+ ' < / thead > '
+ ' < tbody > ';
data.lastBans.forEach(function(e) {
html += ''
+ '< tr > '
+ ' < td > ' + e.Time + '< / td > '
+ ' < td > ' + e.Jail + '< / td > '
+ ' < td > ' + e.IP + '< / td > '
+ ' < td > ' + e.LogLine + '< / td > '
+ '< / tr > ';
});
html += '< / tbody > < / table > ';
}
document.getElementById('dashboard').innerHTML = html;
}
// Render banned IPs with "Unban" button
function renderBannedIPs(jailName, ips) {
if (!ips || ips.length === 0) {
return '< em > No banned IPs< / em > ';
}
var content = '< ul class = "list-unstyled mb-0" > ';
ips.forEach(function(ip) {
content += ''
+ '< li class = "d-flex align-items-center mb-1" > '
+ ' < span class = "me-auto" > ' + ip + '< / span > '
+ ' < button class = "btn btn-sm btn-warning" '
+ ' onclick="unbanIP(\'' + jailName + '\', \'' + ip + '\')">'
+ ' Unban'
+ ' < / button > '
+ '< / li > ';
});
content += '< / ul > ';
return content;
}
2025-01-27 11:35:21 +01:00
// Filter IPs on dashboard table
function filterIPs() {
const query = document.getElementById("ipSearch").value.toLowerCase(); // Get the search query
const rows = document.querySelectorAll("#jailsTable .jail-row"); // Get all jail rows
rows.forEach((row) => {
const ipSpans = row.querySelectorAll("ul li span"); // Find all IP span elements in this row
let matchFound = false; // Reset match flag for the row
ipSpans.forEach((span) => {
const originalText = span.textContent; // The full original text
const ipText = originalText.toLowerCase();
if (query & & ipText.includes(query)) {
matchFound = true; // Match found in this row
// Highlight the matching part
const highlightedText = originalText.replace(
new RegExp(query, "gi"), // Case-insensitive match
(match) => `< mark > ${match}< / mark > ` // Wrap match in < mark >
);
span.innerHTML = highlightedText; // Update span's HTML with highlighting
} else {
// Remove highlighting if no match or search is cleared
span.innerHTML = originalText;
}
});
// Show the row if a match is found or the query is empty
row.style.display = matchFound || !query ? "" : "none";
});
}
2025-01-26 20:11:06 +01:00
//*******************************************************************
//* Functions to manage IP-bans : *
//*******************************************************************
2025-01-25 16:21:14 +01:00
// Unban IP
function unbanIP(jail, ip) {
if (!confirm("Unban IP " + ip + " from jail " + jail + "?")) {
return;
}
showLoading(true);
fetch('/api/jails/' + jail + '/unban/' + ip, { method: 'POST' })
.then(function(res) { return res.json(); })
.then(function(data) {
if (data.error) {
alert("Error: " + data.error);
} else {
2025-01-26 20:11:06 +01:00
alert(data.message || "IP unbanned successfully");
2025-01-25 16:21:14 +01:00
}
return fetchSummary();
})
.catch(function(err) {
alert("Error: " + err);
})
.finally(function() {
showLoading(false);
});
}
2025-01-26 20:11:06 +01:00
//*******************************************************************
//* Filter-mod and config-mod actions : *
//*******************************************************************
// Open jail/filter modal and load filter-config
2025-01-25 16:21:14 +01:00
function openJailConfigModal(jailName) {
currentJailForConfig = jailName;
var textArea = document.getElementById('jailConfigTextarea');
textArea.value = '';
document.getElementById('modalJailName').textContent = jailName;
showLoading(true);
fetch('/api/jails/' + jailName + '/config')
.then(function(res) { return res.json(); })
.then(function(data) {
if (data.error) {
alert("Error loading config: " + data.error);
} else {
textArea.value = data.config;
var modalEl = document.getElementById('jailConfigModal');
var myModal = new bootstrap.Modal(modalEl);
myModal.show();
}
})
.catch(function(err) {
alert("Error: " + err);
})
.finally(function() {
showLoading(false);
});
}
2025-01-26 20:11:06 +01:00
// Save filter config for the current opened jail
2025-01-25 16:21:14 +01:00
function saveJailConfig() {
if (!currentJailForConfig) return;
showLoading(true);
var newConfig = document.getElementById('jailConfigTextarea').value;
fetch('/api/jails/' + currentJailForConfig + '/config', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ config: newConfig }),
})
.then(function(res) { return res.json(); })
.then(function(data) {
if (data.error) {
alert("Error saving config: " + data.error);
} else {
2025-01-26 20:11:06 +01:00
//alert(data.message || "Config saved");
console.log("Filter saved successfully. Reload needed? " + data.reloadNeeded);
2025-01-25 16:21:14 +01:00
// Hide modal
var modalEl = document.getElementById('jailConfigModal');
var modalObj = bootstrap.Modal.getInstance(modalEl);
modalObj.hide();
// Show the reload banner
document.getElementById('reloadBanner').style.display = 'block';
}
})
.catch(function(err) {
alert("Error: " + err);
})
.finally(function() {
showLoading(false);
});
}
2025-01-26 20:11:06 +01:00
// Load current settings when opening settings page
2025-01-25 21:43:50 +01:00
function loadSettings() {
showLoading(true);
fetch('/api/settings')
.then(res => res.json())
.then(data => {
2025-01-27 11:09:06 +01:00
// Get current general settings
2025-01-25 21:43:50 +01:00
document.getElementById('languageSelect').value = data.language || 'en';
2025-01-27 11:09:06 +01:00
document.getElementById('debugMode').checked = data.debug || false;
2025-01-26 20:11:06 +01:00
2025-01-27 11:09:06 +01:00
// Get current alert settings
document.getElementById('sourceEmail').value = data.sender || '';
document.getElementById('destEmail').value = data.destemail || '';
2025-01-26 20:11:06 +01:00
// alertCountries multi
const select = document.getElementById('alertCountries');
// clear selection
for (let i = 0; i < select.options.length ; i + + ) {
select.options[i].selected = false;
}
if (!data.alertCountries || data.alertCountries.length === 0) {
// default to "ALL"
select.options[0].selected = true;
} else {
// Mark them selected
for (let i = 0; i < select.options.length ; i + + ) {
let val = select.options[i].value;
if (data.alertCountries.includes(val)) {
select.options[i].selected = true;
}
2025-01-25 21:43:50 +01:00
}
}
2025-01-27 11:09:06 +01:00
// Get current Fail2Ban Configuration
document.getElementById('bantimeIncrement').checked = data.bantimeIncrement || false;
document.getElementById('banTime').value = data.bantime || '';
document.getElementById('findTime').value = data.findtime || '';
document.getElementById('maxRetry').value = data.maxretry || '';
document.getElementById('ignoreIP').value = data.ignoreip || '';
2025-01-25 21:43:50 +01:00
})
.catch(err => {
alert('Error loading settings: ' + err);
})
.finally(() => showLoading(false));
}
2025-01-26 20:11:06 +01:00
// Save settings when hit the save button
2025-01-25 21:43:50 +01:00
function saveSettings(e) {
e.preventDefault(); // prevent form submission
showLoading(true);
const lang = document.getElementById('languageSelect').value;
2025-01-27 11:09:06 +01:00
const debugMode = document.getElementById("debugMode").checked;
const srcmail = document.getElementById('sourceEmail').value;
const destmail = document.getElementById('destEmail').value;
2025-01-25 21:43:50 +01:00
2025-01-26 20:11:06 +01:00
const select = document.getElementById('alertCountries');
let chosenCountries = [];
for (let i = 0; i < select.options.length ; i + + ) {
if (select.options[i].selected) {
chosenCountries.push(select.options[i].value);
}
}
// If user selected "ALL", we override everything
if (chosenCountries.includes("ALL")) {
chosenCountries = ["all"];
2025-01-25 21:43:50 +01:00
}
2025-01-27 11:09:06 +01:00
const bantimeinc = document.getElementById('bantimeIncrement').checked;
const bant = document.getElementById('banTime').value;
const findt = document.getElementById('findTime').value;
const maxre = parseInt(document.getElementById('maxRetry').value, 10) || 1; // Default to 1 (if parsing fails)
const ignip = document.getElementById('ignoreIP').value;
2025-01-25 21:43:50 +01:00
const body = {
language: lang,
2025-01-27 11:09:06 +01:00
debug: debugMode,
sender: srcmail,
destemail: destmail,
alertCountries: chosenCountries,
bantimeIncrement: bantimeinc,
bantime: bant,
findtime: findt,
maxretry: maxre,
ignoreip: ignip
2025-01-25 21:43:50 +01:00
};
fetch('/api/settings', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify(body)
})
2025-01-26 20:11:06 +01:00
.then(res => res.json())
.then(data => {
if (data.error) {
2025-01-27 11:09:06 +01:00
alert('Error saving settings: ' + data.error + data.details);
2025-01-26 20:11:06 +01:00
} else {
//alert(data.message || 'Settings saved');
console.log("Settings saved successfully. Reload needed? " + data.reloadNeeded);
if (data.reloadNeeded) {
document.getElementById('reloadBanner').style.display = 'block';
}
2025-01-25 21:43:50 +01:00
}
2025-01-26 20:11:06 +01:00
})
.catch(err => alert('Error: ' + err))
.finally(() => showLoading(false));
2025-01-25 21:43:50 +01:00
}
// Load the list of filters from /api/filters
function loadFilters() {
showLoading(true);
fetch('/api/filters')
.then(res => res.json())
.then(data => {
if (data.error) {
alert('Error loading filters: ' + data.error);
return;
}
const select = document.getElementById('filterSelect');
select.innerHTML = ''; // clear existing
if (!data.filters || data.filters.length === 0) {
// optional fallback
const opt = document.createElement('option');
opt.value = '';
opt.textContent = 'No Filters Found';
select.appendChild(opt);
} else {
data.filters.forEach(f => {
const opt = document.createElement('option');
opt.value = f;
opt.textContent = f;
select.appendChild(opt);
});
}
})
.catch(err => {
alert('Error loading filters: ' + err);
})
.finally(() => showLoading(false));
}
// Called when clicking "Test Filter" button
function testSelectedFilter() {
const filterName = document.getElementById('filterSelect').value;
const lines = document.getElementById('logLinesTextarea').value.split('\n');
if (!filterName) {
alert('Please select a filter.');
return;
}
showLoading(true);
fetch('/api/filters/test', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
filterName: filterName,
logLines: lines
})
})
.then(res => res.json())
.then(data => {
if (data.error) {
alert('Error: ' + data.error);
return;
}
// data.matches, for example
renderTestResults(data.matches);
})
.catch(err => {
alert('Error: ' + err);
})
.finally(() => showLoading(false));
}
function renderTestResults(matches) {
let html = '< h5 > Test Results< / h5 > ';
if (!matches || matches.length === 0) {
html += '< p > No matches found.< / p > ';
} else {
html += '< ul > ';
matches.forEach(m => {
html += '< li > ' + m + '< / li > ';
});
html += '< / ul > ';
}
document.getElementById('testResults').innerHTML = html;
}
// When showing the filter section
function showFilterSection() {
loadFilters(); // fetch the filter list
document.getElementById('testResults').innerHTML = '';
document.getElementById('logLinesTextarea').value = '';
}
2025-01-26 20:11:06 +01:00
//*******************************************************************
//* Reload fail2ban action : *
//*******************************************************************
// Reload Fail2ban
function reloadFail2ban() {
if (!confirm("It can happen that some logs are not parsed during the reload of fail2ban. Reload Fail2ban now?")) return;
showLoading(true);
fetch('/api/fail2ban/reload', { method: 'POST' })
.then(function(res) { return res.json(); })
.then(function(data) {
if (data.error) {
alert("Error: " + data.error);
} else {
// Hide reload banner
document.getElementById('reloadBanner').style.display = 'none';
// Refresh data
return fetchSummary();
}
})
.catch(function(err) {
alert("Error: " + err);
})
.finally(function() {
showLoading(false);
});
}
2025-01-25 16:21:14 +01:00
< / script >
< / body >
< / html >